[JPA] 2. 스프링 데이터 JPA

Park Yeongseo·2023년 10월 11일
0

원래는 JPA의 영속성 컨텍스트 등부터 순차적으로 다루는 게 낫겠지만, 일단은 Spring Data JPA를 사용하기 위해 이 부분부터 다룹니다

2.1. 스프링 데이터 JPA

스프링 데이터 JPA는 CRUD를 처리하기 위한 공통 인터페이스를 제공하며, 리포지토리 개발 시 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성 및 주입해준다. 따라서 스프링 데이터 JPA를 이용하면 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발 가능할 수 있다.

2.2. 공통 인터페이스 기능

스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공한다. 이 인터페이스를 상속받고 제네릭에 Entity 클래스 및 Entity 클래스의 식별자 타입을 지정해서 사용하면 된다.

JpaRepository 인터페이스의 주요 메소드

(T는 엔티티, ID는 엔티티 식별자 타입, S는 엔티티와 그 자식 타입)

  • save(S) : 새 엔티티 저장, 또는 이미 있는 엔티티를 수정
  • delete(T) : 엔티티 하나 삭제. 내부에서 EntityManager.remove() 를 호출
  • findOne(ID) : 엔티티 하나를 조회. 내부에서 EntityManager.find() 를 호출
  • getOne(ID) : 엔티티를 프록시로 조회. 내부에서 EntityManager.getReference() 를 호출
  • findAll(...) : 모든 엔티티를 조회. 정렬 및 페이징 조건을 파라미터로 제공할 수 있음.

2.3. 쿼리 메소드 기능⭐⭐⭐

메소드 이름으로 쿼리 생성

인터페이스에 메소드만 선언하면 해당 메소드의 이름으로 적절한 JPQL 쿼리를 생성해서 실행한다.

  • 메소드 이름으로 쿼리 생성
  • 메소드 이름으로 JPA NamedQuery 호출
  • @Query 애노테이션으로 리포지토리 인터페이스에 쿼리를 직접 정의

네이밍 방법에 대한 것은 공식 문서를 확인할 것.

이 기능의 경우, 엔티티 필드명이 변경되면 인터페이스에 정의한 메소드 이름도 꼭 같이 변경해줘야 한다.

JPA Named 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 을 사용하면 반환 타입으로는 ListPage 를 사용할 수 있다. 반환 타입이 Page 이면 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출한다.

페이지를 요청해 실제 사용할 때에는 PageRequest 객체를 사용한다. PageRequest 생성자의 첫 파라미터는 현재 페이지, 다음은 조회할 데이터 수를 입력하고, 추가로 정렬 정보도 파라미터로 사용할 수 있다. (첫 페이지 번호는 0)

힌트

@QueryHints 애노테이션을 사용. 이는 JPA 구현체에 전달되는 힌트.

Lock

@Lock 애노테이션을 사용. 락이 무엇인지는 나중에.

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

스프링 데이터 JPA로 리포지토리 개발시 인터페이스만 정의하고 구현체는 만들지 않지만, 메소드를 직접 구현해야 할 때도 있다.

리포지토리 인터페이스의 구현체를 만들 때에는 인터페이스 이름 + Impl 로 이름을 지어야 스프링 데이터 JPA가 사용자 정의 구현 클래스로 인식한다.

2.5. 스프링 데이터 JPA의 구현체

스프링 데이터 JPA가 제공하는 공통 인터페이스는 SimpleJpaRepository클래스가 구현한다.

  • @Repository JPA 예외를 스프링이 추상화한 예외로 변환
  • @Transactional JPA의 모든 변경은 트랜잭션 안에서 이루어져야 한다. 공통 인터페이스 사용 시, 데이터를 변경하는 메소드에 이 애노테이션으로 트랜잭션 처리가 되어 있다. 서비스 계층에서 트랜잭션을 시작하지 않으면 리포지토리에서 트랜잭션을 시작하고, 서비스 계층에서 트랜잭션을 시작한 경우 리포지토리도 해당 트랜잭션을 전파받아서 그대로 사용한다.
  • @Transactional (readOnly = true) : 데이터 조회 메소드에는 readOnly = true 옵션이 적용되어 있다. 데이터를 변경하지 않는 트랜잭션에서 해당 옵션을 사용하면 플러시를 생략해 성능 향상을 얻을 수 있다.
profile
블로그 이전 준비 중

0개의 댓글