2025. 7. 14. 06:22ㆍ코딩 도구/백엔드 개발 (Backend Development)
JPA 정리
이 글은 인프런 김영한님의 "스프링 DB 2편 - 데이터 접근 활용 기술" 강의를 수강하고 정리한 내용이다.
이번 글에서는 JPA의 기본 개념부터 설정, 리포지토리 구현, 예외 처리까지 실제 스프링 프로젝트에 JPA를 도입하는 전체 흐름을 정리한다. JPA는 자바 ORM(Object Relational Mapping)의 표준으로, SQL 중심의 개발 방식에서 벗어나 객체 지향적인 방식으로 데이터를 다룰 수 있도록 도와준다.
1. JPA 도입 배경
SQL 중심적인 개발은 아래와 같은 문제를 가진다:
- 무한 반복되는 CRUD SQL 작성
- 객체를 테이블에 맞추어 모델링
- 객체 그래프 탐색의 한계
- 계층형 아키텍처 분리 어려움
이러한 문제를 해결하기 위해 JPA는 자바 객체 그대로 설계하고, RDB는 관계형 구조 그대로 설계한 뒤 ORM이 중간에서 매핑을 수행한다. JPA는 SQL 매핑을 자동화하여 객체 중심의 설계를 가능하게 하며, 유지보수성과 생산성을 크게 향상시킨다.
2. JPA 설정
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
- JdbcTemplate, MyBatis 의존성은 제거해도 된다.
- spring-boot-starter-data-jpa에는 하이버네이트, JPA API, 스프링 데이터 JPA가 포함되어 있다.
application.properties
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
스프링 부트 3.0 이상에서는 BasicBinder 대신 orm.jdbc.bind=TRACE를 사용한다.
3. Entity 매핑 - Item 예시
@Entity
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
public Item() {}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- @Entity는 해당 객체가 JPA에서 관리되는 엔티티임을 의미
- @Id는 기본키, @GeneratedValue는 자동 생성 전략
- 필드명은 기본적으로 카멜 → 언더스코어로 매핑된다
4. JPA 리포지토리 구현
@Repository
@Transactional
public class JpaItemRepositoryV1 implements ItemRepository {
private final EntityManager em;
public JpaItemRepositoryV1(EntityManager em) {
this.em = em;
}
public Item save(Item item) {
em.persist(item);
return item;
}
public void update(Long id, ItemUpdateDto dto) {
Item findItem = em.find(Item.class, id);
findItem.setItemName(dto.getItemName());
findItem.setPrice(dto.getPrice());
findItem.setQuantity(dto.getQuantity());
}
public Optional<Item> findById(Long id) {
return Optional.ofNullable(em.find(Item.class, id));
}
public List<Item> findAll(ItemSearchCond cond) {
String jpql = "select i from Item i";
boolean hasName = StringUtils.hasText(cond.getItemName());
boolean hasPrice = cond.getMaxPrice() != null;
if (hasName || hasPrice) jpql += " where";
if (hasName) jpql += " i.itemName like concat('%', :itemName, '%')";
if (hasName && hasPrice) jpql += " and";
if (hasPrice) jpql += " i.price <= :maxPrice";
TypedQuery<Item> query = em.createQuery(jpql, Item.class);
if (hasName) query.setParameter("itemName", cond.getItemName());
if (hasPrice) query.setParameter("maxPrice", cond.getMaxPrice());
return query.getResultList();
}
}
@Transactional이 있어야 저장/수정/삭제가 가능하다. 트랜잭션 커밋 시점에 변경 감지를 통해 SQL이 실행된다.
5. JPQL과 동적 쿼리
- JPA는 JPQL(Java Persistence Query Language)을 통해 복잡한 조회도 처리한다.
- SQL과 유사하지만 테이블이 아닌 객체를 기준으로 작성한다.
String jpql = "select i from Item i where i.itemName like concat('%', :itemName, '%') and i.price <= :maxPrice";
TypedQuery<Item> query = em.createQuery(jpql, Item.class);
query.setParameter("itemName", itemName);
query.setParameter("maxPrice", maxPrice);
복잡한 동적 쿼리는 Querydsl로 해결하는 것이 좋다.
6. 예외 변환과 @Repository
- JPA는 예외 발생 시 PersistenceException을 던진다.
- @Repository가 붙어있으면 스프링 AOP가 작동하여 DataAccessException으로 변환된다.
- 스프링 부트는 이를 위해 PersistenceExceptionTranslationPostProcessor를 자동 등록한다.
덕분에 서비스 계층에서는 예외를 일관된 방식으로 처리할 수 있다.
7. JPA의 성능 최적화 기능
1차 캐시와 동일성 보장
- 같은 트랜잭션 안에서는 같은 엔티티를 캐시로 반환한다.
트랜잭션을 지원하는 쓰기 지연
- persist() 호출 시점이 아니라 커밋 시점에 SQL 실행
지연 로딩
- 연관된 엔티티는 실제 접근할 때 쿼리 실행 (프록시 객체 활용)
8. 정리
ORM 매핑 | 객체와 테이블을 자동 매핑해준다 |
EntityManager | JPA의 핵심, DB 접근을 추상화함 |
트랜잭션 처리 | JPA 데이터 변경은 반드시 트랜잭션 필요 |
JPQL | 객체 기준 쿼리 언어, SQL 유사 문법 |
예외 변환 | @Repository 통해 자동 변환 지원 |
성능 최적화 | 캐시, 쓰기 지연, 지연 로딩 제공 |
JPA는 객체 중심 개발을 가능하게 하며, SQL을 직접 다루지 않아도 되는 큰 장점이 있다. 실무에서는 동적 쿼리 문제 해결을 위해 Querydsl과 함께 사용하면 생산성과 유지보수성이 크게 향상된다.
'코딩 도구 > 백엔드 개발 (Backend Development)' 카테고리의 다른 글
[Spring DB] 스프링 데이터 JPA (4) | 2025.07.28 |
---|---|
[Spring DB] JPA 도입 전 반드시 이해해야 할 핵심 개념 (5) | 2025.07.21 |
[Spring DB] MyBatis (2) | 2025.07.07 |
[Spring DB] 데이터 접근 기술 테스트 (0) | 2025.06.30 |
[Spring DB] 스프링 JdbcTemplate (0) | 2025.06.23 |