3 분 소요

스프링 데이터 JPA은 DB로부터 데이터를 주고 받기위해 작성하던 쿼리를 작성하지 않아도 기능을 지원해줌으로써
개발 생산성을 높여준다.

시작

Member 엔티티가 정의돼 있고 PK 타입을 Long으로 설정했다고 하자. 그러면 Member 엔티티와 관련된 기능을
다음과 같이 MemberRepository라는 interface를 정의해줌으로써 사용할 수 있게된다.

public interface MemberRepository extends JpaRepository<Member, Long> {
}
  • JpaRepository를 반드시 상속
    • 제네릭(<>)에는 <엔티티 타입, PK 타입>을 적어야 함
  • 공통 CRUD 제공
    • save() : 엔티티 저장
    • delete() : 엔티티 삭제
    • findById() : 엔티티 1개 id로 조회
    • findAll() : 모든 엔티티 조회

쿼리 메소드

스프링 데이터 JPA에서 제공하는 공통 CRUD 함수는 아주 간단한 쿼리까지만 처리가 가능하다. 쿼리 메소드 기능을 통해
이러한 한계를 좀 더 극복할 수 있다.

메소드 이름으로 쿼리 생성

메소드 명과 파라미터에 대해 정해진 규칙에 맞게 설정해주면 스프링 데이터 JPA가 분석해 JPQL을 생성 후 실행할 수
있도록 한다. findByAge()를 예로 들면, 엔티티를 age를 기준으로 조회한다. 이 때 엔티티는 Collection으로
받을 수 있다. 여러 규칙이 있는데 일부만 정리하면 다음과 같고, 여기에서 자세한 내용을 참조하자.

  • 조회 : find…By로 설정. Collection, Page 리턴
  • 삭제 : delete…By, remove…By로 설정. long 리턴
  • COUNT : count…By로 설정. long 리턴
  • EXISTS : exists…By로 설정. boolean 리턴
  • 엔티티의 필드명 변경 시 반드시 메소드명도 같이 변경해줘야 함!
  • 이름이 길어질 수 있다는 단점이 존재

@Query

@Query를 쓰면 메소드에 직접 쿼리를 작성할 수 있다. 애플리케이션 실행 시점에 문법 오류를 찾을 수 있다는 장점
을 갖고 있고, 위에서 다룬 규칙을 무시한다. 쿼리에 들어갈 파라미터는 @Param으로 적어주면 된다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    @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);
}
  • 메소드 이름으로 정의했을 때 이름이 길어지는 문제점을 해결하기 위해 @Query를 사용

엔티티가 아닌 DTO로 내보내고 싶다면 생성자를 활용해서 다음처럼 만들 수 있다. 하지만 패키지까지 다 써줘야한다.

@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) " + "from Member m join m.team t")
List<MemberDto> findMemberDto();


쿼리에 들어가는 파라미터로 collection도 지원한다. 이 땐 in절로 처리하게 된다.

@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") List<String> names);


페이징과 정렬

순수 JPA에서 페이징은 setFirstResult(), setMaxResults()를 써야했다. 스프링 데이터 JPA에서는 다음과 같이
함수를 설정해주면 된다. 파라미터에 Pageable를 넣어야 함에 주의하자.

Page<Member> findByUsername(String name, Pageable pageable);
Slice<Member> findByUsername(String name, Pageable pageable);
List<Member> findByUsername(String name, Pageable pageable);
  • 리턴 타입이 모두 다르다!
    • Page : count 쿼리까지 추가로 나감
    • Slice : count 쿼리까지 나가지 않음, 뒤에 내용이 더 있는지 1개만 더 확인
    • List : count 쿼리까지 나가지 않음

Page로 리턴하는 경우 count 쿼리가 추가로 나가게 된다. 이 때 count만 구하면 되는데 정렬 등을 할 필요가 없으
므로 count 쿼리만 따로 설정해줄 수 있다.

@Query(value = select m from Member m,
 countQuery = select count(m.username) from Member m)
Page<Member> findMemberAllCountBy(Pageable pageable);


위에서 다룬 Page를 리턴하는 함수 사용 시 Page.map()으로 DTO를 만들 수 있다!

Page<Member> page = memberRepository.findByUsername("yoon", pageRequest);
Page<MemberDto> dtoPage = page.map(m -> new MemberDto());


벌크성 수정 쿼리

여러 엔티티 정보를 한번에 업데이트하는 쿼리를 벌크성 수정 쿼리라고 한다. 순수 JPA에서는 한 엔티티에 대해
업데이트하는 경우 영속성 컨텍스트에서 변경 감지를 통해 알아서 DB로 쿼리가 날라갔어서 업데이트 쿼리를 따로 날려
줄 필요가 없었으나 벌크성 수정 쿼리는 어떤 조건에 맞는 여러 엔티티에 대해 업데이트하는 쿼리를 말한다. 순수 JPA
처럼 수정 쿼리는 직접 작성해줘야 하나 @Modifying를 반드시 명시해야 한다.

@Modifying
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
  • 수정 쿼리든, 삭제 쿼리든 @Modifying 필수
  • @Modifying(clearAutomatically = true) : 일관성 보장을 위해 벌크성 쿼리 호출 후 영속성 컨텍스트 초기화
    • 벌크 연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리를 날리기 때문에 일관성 문제가 발생할 수 있음

@EntityGraph

연관된 엔티티들을 SQL 한번에 조회하는 방법으로 순수 JPA에서는 fetch join을 써왔다. 스프링 데이터 JPA에서는
fetch join을 쿼리에 직접 적어줄 필요없이 다음과 같이 설정할 수 있다. Member 엔티티가 Team 엔티티를 필드로
갖고 있다고(연관관계를 맺고 있다고) 하자.

@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username)
  • 공통 메소드(findAll() 등)든, Query가 들어가는 메소드든 @EntityGraph는 자유롭게 쓸 수 있음
  • LEFT OUTER JOIN 사용

JPA Hint

JPA 구현체에게 제공하는 힌트를 JPA Hint라 한다. 어떤 엔티티를 read only로 만들고 싶을 때 자주 사용한다.

//스프링 데이터 JPA
@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);

//테스트
@Test
public void queryHint() throws Exception {
    memberRepository.save(new Member("member1", 10));
    em.flush();
    em.clear();

    Member member = memberRepository.findReadOnlyByUsername("member1");
    member.setUsername("member2");
    em.flush(); //Update Query 실행X
}
  • read only로 설정했기 때문에 setUsername()을 호출했음에도 업데이트 쿼리 발생X

JPA lock

일관성이 깨지지 않도록 어떤 트랜잭션에 여러 프로세스 또는 스레드가 접근하지 못하게 하는걸 Lock이라 한다.
JPA에서는 다음과 같이 설정할 수 있으며 애플리케이션 성능에 큰 영향을 줄 수 있으므로 주의해서 사용해야 한다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);

카테고리:

업데이트:

댓글남기기