원래는 JPA의 영속성 컨텍스트 등부터 순차적으로 다루는 게 낫겠지만, 일단은 Spring Data JPA를 사용하기 위해 이 부분부터 다룹니다
스프링 데이터 JPA는 CRUD를 처리하기 위한 공통 인터페이스를 제공하며, 리포지토리 개발 시 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성 및 주입해준다. 따라서 스프링 데이터 JPA를 이용하면 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발 가능할 수 있다.
스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공한다. 이 인터페이스를 상속받고 제네릭에 Entity 클래스 및 Entity 클래스의 식별자 타입을 지정해서 사용하면 된다.
(T는 엔티티, ID는 엔티티 식별자 타입, S는 엔티티와 그 자식 타입)
save(S)
: 새 엔티티 저장, 또는 이미 있는 엔티티를 수정delete(T)
: 엔티티 하나 삭제. 내부에서 EntityManager.remove()
를 호출findOne(ID)
: 엔티티 하나를 조회. 내부에서 EntityManager.find()
를 호출getOne(ID)
: 엔티티를 프록시로 조회. 내부에서 EntityManager.getReference()
를 호출findAll(...)
: 모든 엔티티를 조회. 정렬 및 페이징 조건을 파라미터로 제공할 수 있음.인터페이스에 메소드만 선언하면 해당 메소드의 이름으로 적절한 JPQL 쿼리를 생성해서 실행한다.
@Query
애노테이션으로 리포지토리 인터페이스에 쿼리를 직접 정의네이밍 방법에 대한 것은 공식 문서를 확인할 것.
이 기능의 경우, 엔티티 필드명이 변경되면 인터페이스에 정의한 메소드 이름도 꼭 같이 변경해줘야 한다.
스프링 데이터 JPA는 메소드 이름으로 JPA Named 쿼리를 호출하는 기능을 제공한다.
JPA Named Query
쿼리에 이름을 부여해서 사용하는 방법. 애노테이션이나 XML에 쿼리를 정의할 수 있다.
마찬가지로 Named Native Query도 지원한다.
/*
* 예시
*/
@Entity
@NamedQuery(
name="Member.findByUserName",
query="select m from Member m where m.username = :username")
public class Member {
...
}
//-----------------------------------------------------------
public interface MemberRepository
extends JPARepository<Member, Long> {
List<Member> findByUserName(@Param("username") String username);
}
스프링 데이터 JPA는 선언한 “도메인 클래스 + ‘.’ + 메소드 이름” 으로 Named Query를 찾아서 실행하고, 만약에 Named Query가 없으면 메소드 이름으로 쿼리 생성 전략을 사용한다.
메소드 파라미터에 사용한 @Param
은 이름 기반 파라미터를 바인딩할 때 사용하는 애노테이션이다.
@Query
. 리포지토리 메소드에 쿼리 정의메소드에 직접 정적 쿼리를 작성할 수도 있다.
/*
* JPQL 쿼리
*/
public interface MemberRepository
extends JPARepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")//1부터 시작
List<Member> findByUserName(String username);
}
/*
* 네이티브 SQL
*/
public interface MemberRepository
extends JPARepository<Member, Long> {
@Query(value = "SELECT * FROM MEMBER WHERE USERNAME = ?0",
nativeQuery = true) //0부터 시작
List<Member> findByUserName(String username);
}
스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원한다. 위 코드의 경우 위치 기반 파라미터 바인딩을 사용하고 있다. 가독성 및 유지 보수를 위해서는 이름 기반 파라미터 바인딩을 사용하는 게 당연히 좋다.
벌크성 수정 쿼리란 한 번에 모든 내용을 수정해야 할 때 날리는 쿼리이다. @Modifying
애노테이션을 사용하면 된다. 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트를 초기화하고 싶은 경우에는 clearAutomatically
옵션을 true
로 설정하면 된다.(default false
)
반환 결과가 한 건 이상이면 컬렉션 인터페이스를, 단건이면 반환 타입을 지정한다.
조회 결과가 없을 때, 컬렉션은 빈 컬렉션을, 단건은 null
을 반환한다.
Optional<T>
을 사용해서null
인 경우를 체크해주도록 합시다
단건을 기대하고 반환 타입을 지정했는데 결과가 2건 이상인 경우에는 NonUniqueResultException
예외가 발생한다.
쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 두 가지 특별한 파라미터를 제공한다.
org.springframework.data.domain.Sort
정렬 기능org.springframework.data.domain.Pageable
페이징 기능(내부에 Sort
를 포함)파라미터에 Page
을 사용하면 반환 타입으로는 List
나 Page
를 사용할 수 있다. 반환 타입이 Page
이면 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count
쿼리를 추가로 호출한다.
페이지를 요청해 실제 사용할 때에는 PageRequest
객체를 사용한다. PageRequest
생성자의 첫 파라미터는 현재 페이지, 다음은 조회할 데이터 수를 입력하고, 추가로 정렬 정보도 파라미터로 사용할 수 있다. (첫 페이지 번호는 0)
@QueryHints
애노테이션을 사용. 이는 JPA 구현체에 전달되는 힌트.
@Lock
애노테이션을 사용. 락이 무엇인지는 나중에.
스프링 데이터 JPA로 리포지토리 개발시 인터페이스만 정의하고 구현체는 만들지 않지만, 메소드를 직접 구현해야 할 때도 있다.
리포지토리 인터페이스의 구현체를 만들 때에는 인터페이스 이름 + Impl
로 이름을 지어야 스프링 데이터 JPA가 사용자 정의 구현 클래스로 인식한다.
스프링 데이터 JPA가 제공하는 공통 인터페이스는 SimpleJpaRepository
클래스가 구현한다.
@Repository
JPA 예외를 스프링이 추상화한 예외로 변환@Transactional
JPA의 모든 변경은 트랜잭션 안에서 이루어져야 한다. 공통 인터페이스 사용 시, 데이터를 변경하는 메소드에 이 애노테이션으로 트랜잭션 처리가 되어 있다. 서비스 계층에서 트랜잭션을 시작하지 않으면 리포지토리에서 트랜잭션을 시작하고, 서비스 계층에서 트랜잭션을 시작한 경우 리포지토리도 해당 트랜잭션을 전파받아서 그대로 사용한다.@Transactional (readOnly = true)
: 데이터 조회 메소드에는 readOnly = true
옵션이 적용되어 있다. 데이터를 변경하지 않는 트랜잭션에서 해당 옵션을 사용하면 플러시를 생략해 성능 향상을 얻을 수 있다.