[Spring] JPA 소개 및 사용 예시

728x90

 

JPA 소개

- SQL, 데이터 중심의 설계에서 객체 중심의 설계로 전환할 수 있다.

- 기존의 반복 코드, 기본적인 SQL 문도 JPA 가 직접 만들어 실행한다.

- PK 기반이 아닌 SQL 문은 직접 작성해야 한다.

- 동적 SQL 을 해결하기 어렵다.

- EntityManger 을 주입받아 동작한다.

- JPA 를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.

 

> 참고 <

- 스프링 부트가 JPA 설정에 따라 EntityMangerFactory 를 생성하고, 이를 통해 EntityManager 를 스프링에 주입할 수 있도록 해준다.

 

초기 설정

build.gradle 파일에 다음 설정을 추가해 준다.

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

(JPA 라이브러리에 JDBC 라이브러리도 함께 포함되어 있기 때문에, JDBC 의존관계를 제거해도 된다.)

 

`main` 과 `test` 의 application.properties 에 다음 설정을 추가해 준다.

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE

`org.hibernate.SQL=DEBUG` : 하이버네이트가 생성하고 실행하는 SQL 을 `logger` 를 통해 확인할 수 있다.

`logging.level.org.hibernate.orm.jdbc.bind=TRACE` : SQL 에 바인딩되는 파라미터를 확인할 수 있다.

(`spring.jpa.show-sql=true` 도 실행하는 SQL 을 확인할 수 있지만, `System.out` 을 통해 출력되기 때문에 권장하지 않는다.)

 

JPA 사용 예시

ORM 매핑

@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 가 사용하는 객체라는 뜻으로 엔티티라 불린다.

  : 이 애노테이션이 있어야 JPA 가 인식할 수 있다.

- `@Id`

  : 테이블의 PK 와 해당 필드를 매핑한다.

- `@GeneratedValue(strategy = GenerationType.IDENTITY)`

  : PK 생성 값을 데이터베이스에서 생성하는 IDENTITY 방식을 사용한다.

- `@Column` 

  : 객체의 필드를 테이블의 칼럼과 매핑한다.

  : length 속성은 JPA 매핑 정보로 DDL 을 생성할 때, 칼럼의 길이 값으로 활용한다.

  : 생략한 경우, 필드의 이름을 자동으로 snake_case 표기법으로 변환하여 테이블의 칼럼 이름으로 사용한다.

- `@Table`

  : 테이블 명을 지정할 수 있고, 객체명이랑 같으면 생략할 수 있다.

- `public` or `protected` 기본 생성자가 필수이므로 생략하면 안 된다.

 

리포지토리

@Repository
@Transactional
public class JpaItemRepository implements ItemRepository {

  private final EntityManager em;
  
  public JpaItemRepositoryV1(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";
    // 동적 쿼리 생략
    List<Item> result = em.createQuery(jpql, Item.class).getResultList();
    return result;
}

스프링을 통해 EntityManager 를 주입받아 사용한다.

이때, JPA 의 모든 동작은 EntityManager 를 통해 이뤄지며, 내부에 DataSource 를 가지고 데이터베이스에 접근할 수 있다.

 

JPA 의 모든 데이터의 변경은 트랜잭션 안에서 이뤄져야 하기 때문에,

서비스 계층 또는 리포지토리에 `@Transactional` 트랜잭션을 걸어준다.

 

Insert

- `em.persist()` : EntityManager 가 제공하는 JPA 에서 객체를 테이블에 저장할 때 사용하는 메서드이다.

- PK 키 생성 전략을 IDENTITY 로 사용했기 때문에 JPA 가 실행한 Insert SQL 문을 보면 id 값이 빠져 있는 것을 알 수 있다.

  쿼리 실행 이후 객체의 id 필드에 데이터베이스가 생성한 PK 값을 넣어준다.

Update

- 메서드를 호출하지 않아도 JPA 는 트랜잭션이 커밋되는 시점에 변경된 엔티티 객체를 확인하여 자동으로 SQL 을 실행한다.

  따라서 커밋되는 시점에 Update SQL 문을 실행하고, 롤백되면 Update SQL 문을 실행하지 않는다.

Select

- 엔티티 객체를 PK 기준으로 조회할 때는 `find(조회 타입, PK 값)` 를 사용한다.

  그러면 JPA 가 Select SQL 문을 실행하고 결과를 객체로 변환해 준다.

- PK 기준으로 조회하지 않고, 여러 복잡한 조건으로 조회하려면 JPQL 을 사용하면 된다.

  (동적 쿼리 문제는 Querydsl 도 함께 사용하여 해결할 수 있다.)

 

JPQL

- Java Persistence Query Language 로 JPA 가 제공하는 객체 쿼리 언어이다.

- 여러 데이터를 복잡한 조건으로 조회할 때 사용한다.

- 테이블 대상이 아닌, 엔티티 객체를 대상으로 SQL 문을 실행한다.

- from 다음 테이블명이 아닌 엔티티 객체 이름을 사용하며, 엔티티 객체와 속성 이름은 대소문자를 구분한다.

- JPQL 을 실행하면 엔티티 객체와 매핑 정보를 활용하여 SQL 을 만들게 된다.

// JPQL
select i from Item i
where i.itemName like concat('%',:itemName,'%')
  and i.price <= :maxPrice
// 실행된 SQL
select
  item0_.id as id1_0_,
  item0_.item_name as item_nam2_0_,
  item0_.price as price3_0_,
  item0_.quantity as quantity4_0_
from item item0_
where (item0_.item_name like ('%'||?||'%'))
  and item0_.price<=?

`:` 를 사용하여 파라미터를 입력하고 `setParameter("itemName", itemName)` 을 통해 파라미터 바인딩한다.

 

EntityManager 예외 추상화

순수한 JPA 기술로 예외가 발생하면 JPA 표준 예외를 발생시킨다.

따라서 스프링이 @Repository가 붙은 클래스에 대해 JPA 예외를 스프링 예외 추상화인 `DataAccessException` 으로 변환해 준다.

 

1. 스프링과 JPA 를 함께 사용하는 경우 스프링이 JPA 예외 변환기인 `PersistenceExceptionTranslator` 인터페이스를 자동으로 등록한다.

2. 또한 @Repository 가 붙은 클래스에 AOP 프록시를 생성하고, 예외 변환 어드바이저를 등록하는 `PersistenceExceptionTranslationPostProcesser` 빈 후처리기를 자동으로 등록한다.

  (따라서 단순히 @Repository 만 붙여도 예외 변환이 적용된다.)

3. EntityManager 사용 중 JPA 예외가 발생하면, AOP 프록시가 해당 예외를 감지해 스프링 예외 추상화로 변환한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 DB 2(김영한) - 인프런

728x90