jpa, spring-data-jpa 시작하기, 메서드 구현

Spring-data-jpa 란?

  • spring-data-jpa 는 스프링을 아주 편리하게 사용하도록 도와주는 라이브러리이다.
  • 반복되는 공통 매서드를 미리 구현했다. spring-data의 인터페이스를 상속한다.

사용법

public interface MemberRepository extends JpaRepository<Member, Long> {

}
  • JpaRepository 상속받은 interface로 구현한다.
  • @Repository 혹은 컴퍼넌트로의 선언이 필요 없다. 해당 인터페이스는 jpa가 자동으로 감지하여 구현한다.

공통 인터페이스의 적용

  • JpaRepository는 org.springframework.data.jpa.repository을 패키지로 한다.
  • JpaRepository가 상속하는 인터페이스 PagingAndSortingRepository, Repository 등은 org.springframework.data.repository 을 패키지로 한다.
  • spring-data-jpa 는 spring-data 을 상속한다. 해당 인터페이스를 구현한 라이브러리 중 하나가 spring-data-jpa 이다.
  • spring-data를 상속하는 기능 간 기능과 사용법이 예측 가능하다는 장점이 있다.

공통 언티페이스를 넘어서

  • spring-data는 스펙으로 find, findAll, save, delete 등을 설정하였다. jpa는 이를 구현하였고, interface에 구현 없이 사용 가능하다.
public interface MemberRepository extends JpaRepository<Member, Long> {
    // List<Member> findAllById(Long memberId); 
    // 메서드 없이 동작한다. 왜냐하면 부모 인터페이스에서 이미 해당 기능을 구현했기 때문이다. 
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    // 중략
    
    @Override
    List<T> findAllById(Iterable<ID> ids);

    // 중략
}
  • 만약, spring-data-jpa 에서 이미 스펙으로 선언한 메서드 이외에 다른 메서드를 사용하고 싶으면 어떠한가? findByUsername(String name)
  • 도메인에 특화된 메서드을 스펙을 통해 정의하기에는 다소 어폐가 있다. 도메인에 특화되었다고 하여 이러한 메서드를 모두 직접 jpql로 구현해야 하는가?
public List<Member> findByUsernameAndAgeGreaterThan(String username, int age){
    return em.createQuery("select m from Member m where m.username = :username and m.age > :age", Member.class)
            .setParameter("username", username)
            .setParameter("age", age)
            .getResultList();
}
  • spring-data-jpa는 이에 대한 세 가지 대안을 제공한다. 쿼리 메서드, 네임드 메서드, 인터페이스 메서드.

쿼리 메서드

  • 필드의 이름을 메서드의 이름으로 조합하여 쿼리를 생성한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findMemberByUsernameAndAgeGreaterThan(String username, int age);
}
  • https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
  • {명령}…by…by 의 형태의 문법을 가진다.
  • find / count / exsist / delete / remove / distinct / limit 등을 제공한다.
  • 간단하고 짧은 쿼리에 대해서 자주 사용한다.
  • 이름이 너무 길어지는 단점이 있다.
  • 필드명이 변경될 경우 매서드 이름도 변경해야 한다. 매서드명이 잘못되더라도 이 에러는 어플리케이션 로딩 시점에서 잡아준다.

NamedQuery

코드

  • 쿼리를 작성한 다음, 해당 쿼리의 명칭으로 sql을 검색, 사용한다.

  • 엔티티

@NamedQuery(
        name="Member.findByUsername",
        query="select m from Member m where m.username = :username"
)
public class Member {
    // 후략
}
  • 구현
public List<Member> findByUsername(String username){
    return em.createNamedQuery("Member.findByUsername", Member.class)
            .setParameter("username", username)
            .getResultList();
}

설명

  • 엔티티로 구현하거나, xml을 통해 외부에서 구현할 수도 있다.
  • 네임드쿼리의 역시 sql 쿼리의 오류를 어플리케이션 로딩 시점에서 찾아준다.
  • 다만, 인터페이스 메서드가 더 편리하고 유연하기 때문에 자주 사용하지 않는다.

@Query, 리포지토리에서 메서드 바로 정의하기

  • 쿼리를 직접 짜고 싶은 경우가 있다. 이를 구현체로 구현하지 않는다(만약 그러면 모든 인터페이스의 매서드를 구현해야 한다). @Query 어너테이션을 통해 jpql로 쿼리를 작성한다.
  • jpql로 유연하게 쿼리를 짤 수 있어서 편하다.
  • 어플리케이션 로딩 시점에서 쿼리의 오류를 잡아준다.
public interface MemberRepository extends JpaRepository<Member, Long> {
    
    // entity로 리턴
    @Query("select m.username from Member m")
    List<String> findUsernameList();

    // 인자는 @Param으로 한다. 
    @Query("select m from Member m where m.username = :username and m.age > :age")
    List<Member> findUser(@Param("username") String username, @Param("age") int age);

    // dto로 반환할 수 있다. 
    @Query("select new qoch.datajpa.dto.MemberDto(m) from Member m")
    List<MemberDto> findMemberDto();

    // 인자를 collect 형태로 할 수 있다. 
    @Query("select new qoch.datajpa.dto.MemberDto(m) from Member m where m.username in :names")
    List<MemberDto> findMemberDto(@Param("names") List<String> names);

}