Skip to content

Latest commit

 

History

History
184 lines (160 loc) · 8.55 KB

[민하] 12장.md

File metadata and controls

184 lines (160 loc) · 8.55 KB

스프링 데이터 JPA

Introduction

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 문제를 세련된 방법으로 해결할 수 있다

Body

1. 스프링 데이터 JPA 소개

  • 스프링 데이터 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을 실행한다

2. 공통 인터페이스 기능

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(...) 모든 엔티티를 조회한다. 정렬이나 페이징 조건을 파라미터로 제공할 수 있다

3. 쿼리 메소드 기능

  • 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능은 크게 3가지가 있다
    • 메소드 이름으로 쿼리 생성
    • 메소드 이름으로 JPA NamedQuery 호출
    • @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의

3-1. 메소드 이름으로 쿼리 생성

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

3-2. JPA NamedQuery

@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 쿼리를 실행한다

3-3. @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의

public interface MemberRepository extends JpaRepository<Member, Long> {
    @Query("select m from Member m where m.username = ?1")
    Member findByUsername(String username);
}
  • @Query 어노테이션을 사용해 리포지토리 메소드에 직접 쿼리를 정의할 수 있다
  • 이 방법은 실행할 메소드에 정적 쿼리를 직접 작성해 이름 없는 Named 쿼리라 할 수 있다

3-4. 파라미터 바인딩

select m from Member m where m.username = ?1 //위치 기반
select m from Member m where m.username = :name //이름 기반
  • 스프링 데이터 JPA는 위치 기반 파라미터 바인딩(파라미터 순서로 바인딩, 기본값)과 이름 기반 파라미터 바인딩을 모두 지원한다
  • 이름 기반 파라미터 바인딩을 사용하려면 3-2의 코드 처럼 @Param 어노테이션을 사용하면 된다

4. 사용자 정의 리포지토리 구현

  • 스프링 데이터 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 {
	
}