데이터 접근 기술 _ JPA

이동건 (불꽃냥펀치)·2025년 2월 25일
0

JPA 시작

JPA는 ORM 데이터 접근 기술을 제공

JPA 설정

spring-boot-starter-data-jpa 라이브러리를 사용하면 JPA와 스프링 데이터 JPA를 스프링 부트와 통합하고 설정도 간단히 할 수 있다

application.properties

 logging.level.org.hibernate.SQL=DEBUG
 logging.level.org.hibernate.orm.jdbc.bind=TRACE
  • 다음과 같이 로드를 설정해야 logger를 통해 sql이 출력됨

    JPA 적용 1

    Item

 @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.Indetity): PK 생성값을 데이터 베이스에서 생성하는 identity 방식 사용 (auto_increment)
  • @Column: 객체의 필드를 테이블의 칼럼과 매핑
    • name: 객체는 itemName이지만 테이블의 컬럼은item_name이므로 이렇게 매핑
    • length=10: varchar(10)
    • @Column을 생략하면 필드의 이름을 테이블 컬럼 이름으로 사용

JpaRepositoryV1

@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 = "selectxxx i from Item i";

       Integer maxPrice = cond.getMaxPrice();
       String itemName = cond.getItemName();

       if (StringUtils.hasText(itemName) || maxPrice != null) {
           jpql += " where";
       }

       boolean andFlag = false;
       List<Object> param = new ArrayList<>();
       if (StringUtils.hasText(itemName)) {
           jpql += " i.itemName like concat('%',:itemName,'%')";
           param.add(itemName);
           andFlag = true;
       }

       if (maxPrice != null) {
           if (andFlag) {
               jpql += " and";
           }
           jpql += " i.price <= :maxPrice";
           param.add(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();
   }
}
  • private final EntityManager em: 생성자를 보면 스프링을 통해 엔터티 매니저라는 것을 주입 받은 것을 확인할 수 있다. JPA의 모든 동작은 엔터티 매니저를 통해서 이루어진다. 엔타티 매니저는 내부에 데이터소스를 가지고 있고 데이터베이스에 접근할 수 있다

  • `@Transactional`: JPA의 모든 데이터 변경은 트랜잭션 안에서 이뤄져야 함

    JpaConfig

Configuration
public class JpaConfig {

    private final EntityManager em;

    public JpaConfig(EntityManager em) {
        this.em = em;
    }

    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }

    @Bean
    public ItemRepository itemRepository() {
        return new JpaItemRepository(em);
    }

}



JPA 적용 2

public Item save(Item item)

public Item save(Item item) {
       em.persist(item);
       return item;
   }
  • JPA에서 객체를 테이블에 저장할 때 엔터티 매니저가 제공하는 persist() 메서드를 사용하면 됨 실행된 쿼리
    insert into item (id, item_name, price, quantity) values (null, ?, ?, ?)
    insert into item (id, item_name, price, quantity) values (default, ?, ?, ?)
    insert into item (item_name, price, quantity) values (?, ?, ?)
  • Item entity를 만들때 PK 값 전략을 IDENTITY로 사용했기 때문에 JPA가 이런 쿼리 생성
  • 쿼리 실행 이후에 Item 객체의 id 필드에 데이터베이스가 생성한 PK값이 들어가게 된다

public void update(Long itemId, ItemUpdateDto updateParam)

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());
   }
  • 실행된 쿼리
    update item set item_name=?, price=?, quantity=? where id=?
  • em.update()같은 값을 전혀 호출하지 않았다.
  • JPA는 트랜잭션이 커밋되는 시점에 변경된 엔티티 객체가 있는지 확인한다. 특정 엔터티 객체가 변경된 경우에는 UPDATE SQL을 실행

public Optional findById(Long id)

public Optional<Item> findById(Long id) {
       Item item = em.find(Item.class, id);
       return Optional.ofNullable(item);
   }
 
  • 실행된 쿼리 값

    select
     item0_.id as id1_0_0_,
     item0_.item_name as item_nam2_0_0_,
     item0_.price as price3_0_0_,
     item0_.quantity as quantity4_0_0_
    from item item0_
    where item0_.id=?
    
    




JPA 적용 3 _ 예외변환

  • EntityManager 는 순수한 JPA 기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.

  • JPA는 PersistenceException 과 그 하위 예외를 발생시킨다.

  • 추가로 JPA는 IllegalStateException , IllegalArgumentException 을 발생시킬 수 있다. 그렇다면 JPA 예외를 스프링 예외 추상화( DataAccessException )로 어떻게 변환할 수 있을까?

  • 비밀은 바로 @Repository 에 있다.





    출처:https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2

profile
자바를 사랑합니다

0개의 댓글

관련 채용 정보