데이터 접근 기술 - JPA

2025. 10. 24. 12:49·JPA

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
'JPA' 카테고리의 다른 글
  • 프록시와 연관관계 관리
  • 고급 매핑 - 상속 관계, Mapped Superclass
  • 엔티티 매핑, 연관관계
  • 영속성 관리 - 내부 동작 방식
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (159)
      • Kotlin (31)
      • Java (56)
      • Spring (44)
      • JPA (6)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    언체크예외
    빈 생명주기
    제네릭
    김영한의 실전자바
    다형성
    java
    예외처리
    배열
    캡슐화
    Di
    김영한의 실전 자바
    트랜잭션
    kotlin
    싱글톤
    중첩클래스
    spring
    OCP
    내일배움캠프
    김영한
    jpa
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
데이터 접근 기술 - JPA
상단으로

티스토리툴바