JPA 설정
스프링이 DI 컨테이너를 포함한 애플리케이션 전반의 다양한 기능을 제공한다면, JPA는 ORM 데이터 접근 기술을 제공
(+) 생산성 증가
(+) SQL 중심의 개발에서 객체 중심의 개발
(+) 애플리케이션과 DB 사이에 JPA가 존재한다고 보면됨 : 1차 캐시 지원
build.gradle 의존관계 추가 및 제거
spring-boot-starter-data-jpa 라이브러리를 사용하면 JPA와 스프링 데이터 JPA를 스프링 부트와 통합하고, 설정도 간단히 할 수 있음

-> spring-boot-starter-data-jpa가 spring-boot-starter-jdb도 함께 포함하므로 주석처리
-> 참고로, mybatis-spring-boot-starter도 spring-boot-starter-jdbc를 포함함
이렇게 설정할 시 다음과 같은 3개의 라이브러리가 추가된다.
1) hibernate-core : JPA 구현체인 하이버네이트 라이브러리
2) jakarta.persistence-api : JPA 인터페이스
3) spring-data-jpa : 스프링 데이터 JPA 라이브러리
application.properties 추가

-> org.hibernate.SQL=DEBUG : 하이버네이트가 생성하고 실행하는 SQL 확인 가능
-> org.hibernate.type.descriptor.sql.BasicBinder=TRACE : SQL에 바인딩 되는 파라미터 확인 가능
* 스프링 부트 3.0이상을 사용하면 하이버네이트 6버전이 사용되는데, 로그 방식이 달라지므로 다음과 같이 설정해야 함
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
JPA 적용
JPA에서 가장 중요한 부분은 객체와 테이블을 매핑하는 것
Item.java : Item 객체와 테이블 매핑

-> @Entity : JPA가 사용하는 객체라는 뜻으로, 이 애노테이션으로 JPA가 인식함
-> @Id : 테이블의 PK와 해당 필드 매핑
-> @GeneratedValue(strategy = GenerationType.IDENTITY) : PK 생성 값을 DB에서 생성하는 IDENTITY 방식을 사용
-> @Column : 객체의 필드를 테이블 컬럼과 매핑
-> JPA는 public 또는 protected의 기본 생성자가 필수!
JpaItemRepositoryV1.java
package hello.itemservice.repository.jpa;
import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
@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); // 매핑 정보를 가지고 insert 쿼리를 가지고 저장해줌
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
Item findItem = em.find(Item.class, itemId);
// JPA가 내부에 스냅샷을 떠놓고 트랜잭션이 커밋되는 시점에 해당 데이터들에 대한
// update 쿼리를 만들고 DB에 날림
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"; // i는 Item 엔티티 자체
List<Item> result = em.createQuery(jpql, Item.class).getResultList();
return result;
}
}
-> private final EntityManager
: 생성자를 보면 스프링을 통해 엔티티 매니저를 주입 받는다.
: JPA의 모든 동작은 엔티티 매니저를 통해 이루어진다.
: 엔티티 매니저는 내부에 데이터 소스를 가지고 있고, 데이터베이스에 접근할 수 있다.
-> @Transactional
: JPA의 모든 데이터 변경은 트랜잭션 안에서 이루어져야 한다(조회는 트랜잭션 없이도 가능)
: 변경의 경우, 일반적으로 서비스 계층에서 트랜잭션을 시작하기 때문에 문제가 없다.
: 일반적으로는 비지니스 로직을 시작하는 서비스 계층에 트랜잭션을 걸어주는 것이 맞음, 현재 예제에는 복잡한 비지니스 로직이 없으므로 리포지토리에 트랜잭션을 걸어주었다.
JPA 리포지토리 분석
1. 저장 : save()
em.persist() : JPA에서 객체를 테이블에 저장할 때는 엔티티 매니저가 제공하는 persist() 메서드를 사용


-> JPA가 만들어 실행한 SQL에 id가 빠져있는 것은, PK 키 생성 전략을 IDENTITY로 사용했기 때문(DB가 생성한 PK값이 들어가게 됨)
2. 수정 : udpate()

-> em.update() 같은 메서드가 없는데 어떻게 UPDATE SQL이 실행되는가?
-> JPA는 트랜잭션이 커밋되는 시점에 변경된 엔티티가 있는지 확인하고 특정 엔티티 객체가 변경된 경우 UPDATE SQL을 실행한다.
3. 단건 조회 : findByID()

-> 별칭이 조금 복잡한 것으로 확인 : 조인이 발생하거나 복잡한 조건에서도 문제 없도록 기계적으로 만들다보니 이런 것으로 추측
4. 목록조회 : findAll() - 동적 쿼리
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();
}
* JPQL(Java Persistence Query Language)
- JPA는 JPQL이라는 객체지향 쿼리 언어 제공
- 주로 여러 데이터를 복잡한 조건으로 조회할 때 사용
- SQL이 테이블을 대상으로 한다면, JPQL은 엔티티 객체를 대상으로 SQL을 실행한다고 생각하면 됨
- JPQL을 실행하면 그 안에 포함된 엔티티 객체의 매핑 정보를 활용해 SQL을 만들게 됨
=> JPA를 사용해도 동적 쿼리 문제는 남아 있음, 실무에서는 동적 쿼리 때문에 JPA 사용 시 Querydsl도 함께 선택함
JPA 예외 변환
JPA의 경우, 예외가 발생하면 JPA 예외가 발생하게 됨
- EntityManager는 순수 JPA 기술이고, 스프링과는 관계가 없음 -> 엔티티 메니저는 예외가 발생하면 JPA 관련 예외를 발생시킴
- JPA는 PersistenceException과 그 하위 예외를 발생시킴
- 어떻게 JPA 예외를 스프링 예외 추상화로 변환할 수 있을까? -> @Repository 기능 사용!!


@Repository의 기능
1) @Repository가 붙은 클래스는 컴포넌트 스캔의 대상이 됨
2) @Repository가 붙은 클래스는 예외 변환 AOP의 적용 대상이 됨
-> 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기(PersistenceExceptionTranslater)를 등록한다.
-> 예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생한 예외를 스프링 데이터 접근 예외로 변환한다.
=> 리포지토리에 @Repository 애노테이션이 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.
=> 스프링 부트는 PersistenceExceptionTranslationPostProcessor를 자동으로 등록하는데, 여기서 @Repository를 AOP 프록시로 만드는 어드바이저가 등록된다.
'인프런 > 스프링 DB 2편 - 데이터 접근 활용 기술' 카테고리의 다른 글
| [인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 7. 데이터 접근 기술 - Querydsl (0) | 2024.08.22 |
|---|---|
| [인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 6. 데이터 접근 기술 - 스프링 데이터 JPA (0) | 2024.08.11 |
| [인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 4. MyBatis (1) | 2024.07.28 |
| [인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 3. 데이터 접근 기술 - 테스트 (2) | 2024.07.22 |
| [인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 2. 데이터 접근 기술 - 스프링 JdbcTemplate (1) | 2024.07.18 |