[Spring] 스프링 데이터 JPA 사용하기

728x90

 

스프링 데이터 JPA 소개

- JPA 를 편리하게 사용하도록 도와주는 프레임워크이다.

- 리파지토리의 구현 클래스 없이 인터페이스만으로 개발이 가능하다.

- 기본 CRUD 기능과 페이지 기능도 자동 제공된다.

- 쿼리 메서드 기능을 제공한다.

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

 

초기 설정

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` 을 통해 출력되기 때문에 권장하지 않는다.)

 

공통 인터페이스 기능

- `JpaRepository` 인터페이스를 통해 기본적인 CRUD 기능을 제공한다.

- 공통화 가능한 기능이 거의 모두 포함되어 있다.

public interface ItemRepository extends JpaRepository<Item, Long> {
}

`JpaRepository` 인터페이스를 인터페이스 상속을 받아, 제네릭에 관리할 `<엔티티, 엔티티ID>` 를 주면 된다.

그러면 `JpaRepository` 가 제공하는 기본 CRUD 기능을 모두 사용할 수 있다.

 

> 참고 <

`JpaRepository` 인터페이스만 상속받으면 스프링 데이터 JPA 가 프록시 기술을 사용하여 구현 클래스를 만들어 스프링 빈으로 자동 등록한다.

  

쿼리 메서드 기능

인터페이스에 메서드만 적어주면, 메서드 이름을 분석하여 쿼리를 자동으로 만들고 실행해주는 기능이다.

public interface MemberRepository extends JpaRepository<Member, Long> {
  List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}

메서드 이름을 분석해서 필요한 JPQL 을 만들어 실행한다.

(JPA 가 JPQL 을 SQL 로 번역하여 실행한다.)

 

조회 : `find_By`, `read_By`, `query_By`, `get_By`

COUNT : `count_By` / 반환타입 long

EXISTS : `exists_By` / 반환타입 boolean

삭제 : `delete_By`, `remove_By` / 반환타입 long

DISTINCT : `findDistinct`, `findMemberDistinctBy`

LIMIT : `findFirst3`, `findFirst`, `findTop`, `findTop3`

 

@Query

- 쿼리를 직접 실행할 수 있다.

- 파라미터를 @Param 애노테이션을 통해 명시적으로 바인딩해야 한다.

@Query("select i from Item i where i.itemName like :itemName and i.price <= :price")
List<Item> findItems(@Param("itemName") String itemName, @Param("price") Integer price);

 

스프링 데이터 JPA 사용 예시

Spring Data JPA 리포지토리

public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {

  List<Item> findByItemNameLike(String itemName);
  List<Item> findByPriceLessThanEqual(Integer price);
  List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);
  
  // 쿼리 직접 실행
  @Query("select i from Item i where i.itemName like :itemName and i.price <= :price")
  List<Item> findItems(@Param("itemName") String itemName, @Param("price") Integer price);
}

`findByItemNameLikeAndPriceLessThanEqual` 과 `findItems` 는 같은 기능을 수행한다.

 

리포지토리

@Repository
@Transactional
@RequiredArgsConstructor
public class JpaItemRepository implements ItemRepository {

  private final SpringDataJpaItemRepository repository;
  
  @Override
  public Item save(Item item) {
    return repository.save(item);
  }
  
  @Override
  public void update(Long itemId, ItemUpdateDto updateParam) {
    Item findItem = repository.findById(itemId).orElseThrow();
    findItem.setItemName(updateParam.getItemName());
    findItem.setPrice(updateParam.getPrice());
    findItem.setQuantity(updateParam.getQuantity());
 }
 
  @Override
  public Optional<Item> findById(Long id) {
    return repository.findById(id);
  }
  
  @Override
  public List<Item> findAll(ItemSearchCond cond) {
    String itemName = cond.getItemName();
    Integer maxPrice = cond.getMaxPrice();
    if (StringUtils.hasText(itemName) && maxPrice != null) {
      return repository.findItems("%" + itemName + "%", maxPrice);
    } else if (StringUtils.hasText(itemName)) {
      return repository.findByItemNameLike("%" + itemName + "%");
    } else if (maxPrice != null) {
      return repository.findByPriceLessThanEqual(maxPrice);
    } else {
      return repository.findAll();
    }
  }
}

Udate

- 트랜잭션이 커밋될 때 변경 내용이 데이터베이스에 반영된다.

 

Spring Data JPA 리포지토리

`JpaRepository` 를 상속한 인터페이스는 스프링 데이터 JPA 내부에서 자동으로 구현 클래스(프록시 객체)를 생성하여 빈으로 등록한다.

따라서 스프링 예외 추상화를 지원하기 때문에, @Repository 와 관계없이 예외가 변환된다.

(하지만 직접 작성하는 리포지토리 구현 클래스에서는 @Repository를 붙여야 예외 변환이 적용된다.

왜냐하면, 스프링은 @Repository 가 붙은 클래스에만 `PersistenceExceptionTranslationPostProcessor` 를 통해 예외 변환 AOP를 적용하기 때문이다.) 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 
728x90