public class MemberRepository {
@PersistenceContext
EntityManager em;
public void save(Member member) { ... }
public Memeber findOne(Long id) { ... }
public List<Member> findAll() { ... }
public Member findByUserName(String username) { ... }
}
public class ItemRepository {
@PersistenceContext
EntityManager em;
public void save(Item item) { ... }
public Member findOne(Long id) { ... }
public List<Member> findAll() { ... }
}
- 위의 코드는 JPA의 반복적인 CRUD 예제이다
- 회원 리포지토리와 상품 리포지토리가 하는 일이 비슷하다
- 이 문제를 해결하려면 제네릭과 상속을 적절히 사용해서 공통 부분을 처리하는 부모 클래스를 만들면 된다(GenericDAO)
- 하지만이 방법은 공통 기능을 구현한 부모 클래스에 너무 종속되고 구현 클래스 상속이 가지는 단점에 노출된다
→ 스프링 데이터 JPA로 지루하게 반복되는 CRUD 문제를 세련된 방법으로 해결할 수 있다
- 스프링 데이터 JPA는 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트이다
- CRUD를 처리하기 위한 공통 인터페이스를 제공
- 리포지토리를 개발할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입
→ 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByUserName(String username);
}
public interface ItemRepository extends JpaRepository<Item, Long> {
}
- intro에서 본 길고 지루한 코드를 간단하게 인터페이스만 작성하는 것으로 바꿀 수 있다
- 스프링 데이터 JPA가 제공하는 org.springframework.data.jpa.repository.JpaRepository 인터페이스에 공통 메소드가 있다
- 회원과 상품 리포지토리 인터페이스의 구현체는 애플리케이션 실행 시점에 스프링 데이터 JPA가 생성해서 주입해주어 개발자가 구현체를 개발하지 않아도 됨
select m from Member m where username =:username
- MemberRepository.findByUsername(...)처럼 직접 작성한 메소드는 어떻게 해야할까?
- 스프링 데이터 JPA는 메소드 이름을 분석해 위의 JPQL을 실행한다
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
...
}
- 스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공한다
public interface MemberRepository extends JpaRepository<Member, Long> {
}
- 스프링 데이터 JPA를 사용하려면 JpaRepository 인터페이스를 상속받아 제네릭에 엔티티 클래스와 식별자 타입을 지정하면 된다
- Repository, CrudRepository, PagingAndSortingRepository는 스프링 데이터 프로젝트가 공통으로 사용하는 인터페이스
- JpaRepository는 스프링 데이터 JPA가 제공하는 인터페이스로 JPA에 특화된 기능을 제공한다
- JpaRepository 상속시 사용할 수 있는 주요 메소드(T는 엔티티, ID는 엔티티의 식별자 타입, S는 엔티티와 그 자식 타입)
메소드 | 기능 |
---|---|
save(S) | 새로운 엔티티는 저장하고 이미 있는 엔티티는 수정한다 |
delete(T) | 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove()를 호출한다 |
findOne(ID) | 엔티티 하나를 조회한다. 내부에서 EntityManager.find()를 호출한다 |
getOne(ID) | 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference()를 호출한다 |
findAll(...) | 모든 엔티티를 조회한다. 정렬이나 페이징 조건을 파라미터로 제공할 수 있다 |
- 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능은 크게 3가지가 있다
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
- @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의
public interface MemberRepository extends Repository<Member, Long> {
List<Member> findByEmailAndName(String email, String name);
}
select m from Member m where m.email =?1 and m.name =?2
- 이메일과 이름으로 회원을 조회하려면 위처럼 메소드를 정의하면 된다
- 인터페이스에 정의한 findByEmailAndName(...) 메소드를 실행하면 스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행한다
- 이렇게 정해진 규칙에 따라 메소드 이름을 지으면 JPA가 알아서 JPQL을 생성하고 실행한다
- 생성 규칙은 다음 링크를 참고하세요:) https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
@Entity
@NamedQuery(
name="Member.findByUsername"
query="select m from Member m where m.username =:username")
public class Memeber {
...
}
<named-query name="Member.findByUsername">
<query><CDATA[
select m
from Member m
where m.username = :username
]></query>
</named-query>
- 스프링 데이터 JPA는 메소드 이름으로 JPA Named 쿼리를 호출하는 기능을 제공한다
- JPA NamedQuery란 이름 그대로 쿼리에 이름을 부여해서 사용하는 방법이다
- 어노테이션이나 XML에 정의하여 사용할 수 있다
public interface MemberRepository extends JpaRepository<Member, Long> { //Member 도메인 클래스
List<Member> findByUsername(@Param("username") String username);
}
- @Param은 이름 기반 파라미터를 바인딩할 때 사용하는 어노테이션이다
- 자세한 것은 3-4 파라미터 바인딩에서
- 스프링 데이터 JPA를 사용하면 위와 같이 메소드 이름만으로 Named 쿼리를 호출할 수 있다
- 스프링 데이터 JPA는 선언한 "도메인 클래스 + . + 메소드 이름"으로 Named 쿼리를 실행한다
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")
Member findByUsername(String username);
}
- @Query 어노테이션을 사용해 리포지토리 메소드에 직접 쿼리를 정의할 수 있다
- 이 방법은 실행할 메소드에 정적 쿼리를 직접 작성해 이름 없는 Named 쿼리라 할 수 있다
select m from Member m where m.username = ?1 //위치 기반
select m from Member m where m.username = :name //이름 기반
- 스프링 데이터 JPA는 위치 기반 파라미터 바인딩(파라미터 순서로 바인딩, 기본값)과 이름 기반 파라미터 바인딩을 모두 지원한다
- 이름 기반 파라미터 바인딩을 사용하려면 3-2의 코드 처럼 @Param 어노테이션을 사용하면 된다
- 스프링 데이터 JPA로 리포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않는다
- 하지만 메소드를 직접 구현해야 할 때도 있다
- 리포지토리를 직접 구현하면 공통 인터페이스가 제공하는 기능까지 모두 구현해야 함
- 스프링 데이터 JPA는 이런 문제를 우회해서 필요한 메소드만 구현할 수 있는 방법을 제공
(1) 직접 구현할 메소드를 위한 사용자 정의 인터페이스를 작성
public interface MemberRepositoryCustom {
public List<Member> findMemberCustom();
}
(2) 사용자 정의 인터페이스를 구현한 클래스를 작성
- 클래스 이름 규칙 : 리포지토리 인터페이스 이름+Impl → 스프링 데이터 JPA가 사용자 정의 구현 클래스로 인식
public class MemberRepositoryImpl implements MemberRepositoryCustom {
@Override
public List<Member> findMemberCustom() {
... //사용자 정의 구현
}
}
(3) 리포지토리 인터페이스에서 사용자 정의 인터페이스를 상속 받기
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}