JPA 적용1 - 개발
package hello.itemservice.domain;
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "item_name", length = 10)
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 : 테이블의 PK와 해당 필드를 매핑
- @GeneratedValue(strategy = GenerationType.IDENTITY) : PK 생성 값을 데이터베이스에서 생성하는 IDENTITY 방식으로 사용
- @Column : 객체의 필드를 테이블의 컬럼과 매핑
@Slf4j
@Repository
@Transactional
public class JpaItemRepository implements ItemRepository {
private final EntityManager em;
public JpaItemRepository(EntityManager em) {
this.em = em;
}
@Override
public Item save(Item item) {
em.persist(item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
Item findItem = em.find(Item.class, itemId);
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
}
@Override
public Optional<Item> findById(Long id) {
Item item = em.find(Item.class, id);
return Optional.ofNullable(item);
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
String jpql = "select i from Item i";
Integer maxPrice = cond.getMaxPrice();
String itemName = cond.getItemName();
if (StringUtils.hasText(itemName) || maxPrice != null) {
jpql += " where";
}
boolean andFlag = false;
if (StringUtils.hasText(itemName)) {
jpql += " i.itemName like concat('%',:itemName,'%')";
andFlag = true;
}
if (maxPrice != null) {
if (andFlag) {
jpql += " and";
}
jpql += " i.price <= :maxPrice";
}
log.info("jpql={}", jpql);
TypedQuery<Item> query = em.createQuery(jpql, Item.class);
if (StringUtils.hasText(itemName)) {
query.setParameter("itemName", itemName);
}
if (maxPrice != null) {
query.setParameter("maxPrice", maxPrice);
}
return query.getResultList();
}
}
- EntityManager : 생성자를 보면 스프링을 통해 엔티티매니저를 주입받는다. JPA의 모든 동작은 엔티티 매니저를 통해 이루어진다. 내부에 데이터소스를 가지고있고, 데이터베이스에 접근할 수 있다.
- @Transactional : JPA의 모든 데이터 변경은 트랜잭션 안에서 이루어져야 한다. 예제에선 리포지토리에 트랜잭션을 걸었지만 일반적으로는 비즈니스 로직을 시작하는 서비스 계층에 트랜잭션을 걸어주는것이 맞다.
JPA 적용2 - 리포지토리 분석
save() - 저장
- em.persist(item) : JPA에서 객체를 테이블에 저장할 땐 엔티티 매니저가 제공하는 persist() 메서드를 사용하면 된다.
JPA가 만들어낸 sql을 보면 id값이 빠져있는데, PK 키 생성 전략이 IDENTITY 라서 그렇다. 물론 쿼리 실행 이후에 id 필드에 설정한 pk값이 들어간다.
update() - 수정
em.update() 같은 메서드 호출이 없는데 update sql이 실행됐다.
- JPA는 트랜잭션이 커밋되는 시점에 변경된 엔티티 객체가 있는지 확인 후, 변경된 경우 update sql을 실행한다.
(자세한건 영속성 컨텍스트 공부)
findById() - 단건 조회
- JPA에서 엔티티 객체를 PK기준으로 조회할 때는 find()를 사용하고 조회 타입과 PK값을 주면 된다. 그럼 sql을 만들어서 실행하고 결과를 객체로 바로 변환해준다.
findAll - 목록 조회
JPQL을 사용했다. (객체지향 쿼리 언어) 주로 여러 데이터를 복잡한 조건으로 조회할 때 사용한다.
SQL이 테이블 대상이라면 JPQL은 엔티티 객체를 대상으로 SQL을 실행한다 생각하면 된다.
동적 쿼리는 JDBC 기반이기 때문에 어쩔 수 없다. querydsl을 사용하면 해결할 수 있다.
JPA 적용3 - 예외 변환
JPA의 경우 예외가 발생하면 JPA 예외가 발생한다.
EntityManager는 순수한 JPA기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.
- JPA는 PersistenceException과 그 하위 예외를 발생시킨다.
추가로 JPA는 IllegalStateException, IllegalARgumentException을 발생시킬 수 있다.
그렇다면 JPA 예외를 스프링 예외 추상화 (DataAccessException)으로 어떻게 변환하는가?
@Repository가 해준다.

@Repository의 기능
1. 컴포넌트 스캔 대상이 됨
2. 예외 변환 AOP의 적용 대상이 됨
- 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA예외 변환기 PersistenceExceptionTranslator를 등록한다.


*스프링부트는 PersistenceExceptionTranslationPostProcessor를 자동으로 등록하는데, 여기에서 @Repository를 AOP프록시로 만드는 어드바이저가 등록된다.
* 실제 JPA 예외변환을 하는 코드는 EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible()이다.
'JPA' 카테고리의 다른 글
| JPA의 값 타입 (0) | 2025.12.27 |
|---|---|
| 프록시와 연관관계 관리 (0) | 2025.12.26 |
| 고급 매핑 - 상속 관계, Mapped Superclass (0) | 2025.12.25 |
| 엔티티 매핑, 연관관계 (0) | 2025.12.12 |
| 영속성 관리 - 내부 동작 방식 (0) | 2025.12.05 |
