#12 스프링 데이터 JPA

김영한 저 "자바 ORM 표준 JPA 프로그래밍, 2015"을 읽고 정리한 내용입니다.

Repository들의 하는 일이 비슷한 경우가 있다. (예를 들어, 메서드 - save, findAll 등) 이때는 GenerericDAO를 이용해 해결하는 방법이 있지만, 부모 클래스에 너무 종속되고 구현 클래스 상속에 가지는 단점에 노출된다.

인터페이스만 작성하면 실행시점에, 스프링 데이터 JPA가 구현객체를 동적으로 생성해서 주입해준다. 즉, 데이터 접근계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다.

구현 예시

//개발자가 직접 구현체를 개발하지 않아도 된다.
public interface MemberRepository extends JpaRepository<Member, Long>{
    Member findByUsername(String username);
}

public interface ItemRepository extends JpaRepository<Item, Long> {
}
  • JpaRepository ->일반적인 CRUD 메소드 제공

  • 위 findByUsername 메소드는 JpaRepository가 제공하지 않지만, 이름을 분석해서 아래의 JPQL을 실행한다. (쿼리 메소드 기능)

select m from Member m where username =:username

스프링 프레임워크와 JPA를 사용하는 환경에서 스프링 데이터 JPA를 꼭 사용하자.

12.1. 설정 (스프링 프레임워크 사용시)

// 1. pom.xml 종속성 설정 
<artifactld>spring-data-jpa</artifactld>

// 2. XML 설정
<jpa:repositories base-package="jpabook.jpashop.repository" />

// 3. 자바 설정 
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}

애플리케이션을 실행할 때 basePackage에 있는 리포지토리 인터페이스들을 찾아서, 해당 인터페이스를 구현한 클래스를 동적으로 생성한 다음 스프링 빈으로 등록한다.

12.2. JpaRepository 주요 메소드

메소드

설명

save(S)

새로운 엔티티는 저장하고 이미 있는 엔티티는 수정한다.

delete(T)

내부에서 EntityManager.remove()를 호출

findOne(ID)

내부에서 EntityManager.find()를 호출

getOne(ID)

엔티티를 프록시로 조회한다. 내부에서 EntityManager. getReferenc()를 호출

findAll(...)

정렬(Sort)이나 페이징(Pageable) 조건을 파라미터로 제공할 수 있다.

12.3. 쿼리 메소드 기능

  1. 메소드 이름으로 쿼리 자동 생성 (위 findByUsername)

  2. 메소드 이름으로 NamedQuery 호출

  3. @Query 사용해서 리포지토리 인터페이스에 쿼리 직접 정의

3.1 메소드 이름으로 쿼리 자동 생성

정해진 규칙에 따라서 메소드 이름을 지어야 한다.

Keyword

Sample

JPQL snippet

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstname, findByFirstnameIs, findByFirstnameEquals

… where x.firstname = 1?

Between

findByStartDateBetween

… where x.startDate between 1? and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age <= ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull

findByAgeIsNull

… where x.age is null

IsNotNull,NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1 (parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1 (parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1 (parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn (Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn (Collection<Age> age)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)

엔티티의 필드명이 변경되면 인터페이스에 정의한 메소드 변경해야 한다.

3.2 NamedQuery 호출

3.3 @Query, 리포지토리 메소드에 쿼리 직접 정의

3.4 벌크성 수정 쿼리

@Modifying벌크성 수정 삭제시 사용

3.5 반환타입 두종류

  • 단건은 getSingleResult() 메소드를 호출

  • 따러서, 두가지 예외 발생 가능 NonUniqueResultException NoResultException

3.6 페이징 처리

  • Sort Pageable 파라미터

  • 반환타입으로 Page를 사용하면, 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출한다.

이름이 "김"으로 시작하는 사람을 찾아 첫번째(0) 페이지부터 10개씩 보여준다.

  • Pageable PageRequest getContent() getTotalPages() hasNextPage()

페이지 인터페이스

3.7 힌트

QueryHints - JPA 구현체에게 제공하는 힌트

forCounting 속 -값추가로 호출 하는 페이징을 위한 count 쿼리에도 쿼리 힌트를 적용할지를 설정하는 옵션()디폴트 true )

3.8 락

16장에서 자세히 배운다.

LockModeType.PESSIMISTIC_WRITE

12.4. 명세

책 Domain Driven Design는 SPECIFICATION 개념을 소개하는데, 스프링 데이터 JPA는 JPA Criteria로 이 개념을 사용할 수 있도록 지원한다. 명세를 이해하기 위해선 핵심단어 술어(predicate)를 알아야 한다.

명세기능을 사용하기 위해 리포지토리에서 JpaSpecificationExecutor를 상속받으면 된다.

JpaSpecificationExecutor인터페이스

이름 명세와 주문상태 명세를 and로 조합해서 검색조건으로 사용한다.

Specifications는 명세들을 조립할 수 있도록 도와주는 클래스이다. (where(), and(), or(), not() 메소드를 제공)

OrderSpec 명세 정의 코드

toPredicate() 메소드 구현하면 JPA Criteria의 Root, CriteriaQuery, CriteriaBuilder 클래스가 모두 파라미터로 주어진다. 이 파라미터들을 활용해서 적절한 검색 조건 을 반환하면 된다. (10장 Criteria부분 참고)

12.5. 사용자 정의 리포지토리

스프링 데이터 JPA로 리포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않는다. 하지만 여 이유로 메소드를 직접 구현해야 할 때도 있다. 그렇다고 리포지토리를 직접 구현하면 공통 인터페이스가 제공하는 기능까지 모두 구현해야 한다. 스프링 데이터 JPA는 이런 문제를 우회해서 필요한 메소드만 구현할 수 있는 방법을 제공한다.

네이밍 규칙 - 리포지토리 인터페이스 이름 + Impl MemberRepositorylmpl

MemberRepositoryCustom을 상속받는다.

12.6. 웹 확장

  • 스프링 MVC에서 사용할 수 있는 편리한 기능을 제공한다.

    • 도메인 클래스 컨버터 기능 - 식별자로 도메인 클래스를 바로 바인딩

    • 페이징과 정렬 기능

  • SpringDataWebConfiguration을 스프링 빈으로 등록해야 된다.

  • JavaConfig 사용시EnableSpringDataWebSupport어노테이션 사용

6.1 도메인 클래스 컨버터 기능

  • /member/memberUpdateForm?id=1

    • 식별자값이 1인 멤버 엔티티를 찾아 온다.

참고로 도메인 클래스 컨버터를 통해 넘어온 회원 엔티티를 컨트롤러에서 직접 수정해도, 실제 데이터 베이스에는 반영되지 않는다. 이것은 OSIV와 관련이 있는데, OSIV 사용 안할시 넘어온 엔티티가 준영속 상태에 있기 때문에 merge 하던가, OSIV를 사용하도록 설정해두면 자동으로 반영되게 된다. (OSIV 13장 확)

6.2 페이징과 정렬 기능

Pageable의 기본값은 page=0, size=20이다.

  • /members?page=0&size=20&sort=name,desc&sort=address.city

    • 이름과 주소의 도시로 정렬하고 첫번째(0) 페이지부터 20개씩 보기.

    • 페이지 1부터 시작하고 싶으면 PageableHandlerMethodArgumentResolver

Pageable은 요청 파라미터(page, size, sort) 정보로 만들어진다. (Pageable은 인터페이스고 실제는 PageRequest 객체가 생성된다)

  • /members?member_page=0&order_page=1

    • 페이징 정보가 둘 이상이면 접두사를 사용해서 구분한다. @Qualifier

12.7. 스프링 데이터 JPA가 사용하는 구현체

공통 인터페이스는SimpleJpaRepository 클래스가 구현한다.

JPA의 모든 변경은 트랜잭션 안에서 이루어져 하기 때문에 @Transactional를 지정하였고, 이로써 서비스 계층에서 트랜잭션을 시작하지 않았어도 리포지토리에서 시작된다. readonly는 변경없는 트랜잭션에서 플러시를 생략해, 성능향상을 준다.

Persistable 인터페이스를 구현하면 엔티티 존재 여부를 판단 로직을 추가할 수 있다.

12.8. 스프링 데이터 JPA와 QueryDSL 통합

QueryDslPredicateExecutor QueryDslRepositorySupport

스프링 데이터 JPA는 위 2가지 방법으로 QueryDSL을 지원한다. 실습참고 arrow-up-right

다음 장에서는 J2EE와 스프링 컨테이너 환경에 서 영속성 컨텍스트가 어떻게 동작하는지 알아보자.

참고 자료

Last updated