스프링 데이터 JPA

inho ha·2022년 5월 15일
0

자바 ORM 표준 JPA 프로그래밍

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788960777330

GenericDAO

데이터 접근 계층은 CRUD 코드를 반복해서 해야한다.
이때 리포지토리들이 하는 일이 비슷해서 중복된 코드가 생긴다.
이를 해결하기 위해 제네릭과 상속을 사용해서 공통 부분을 처리하는 부모 클래스를 만든다.
이를 GenericDAO라고 한다.

하지만 이 방법은 부모 클래스에 종속되고 구현 클래스 상속이 가지는 단점을 가진다.

스프링 데이터 JPA

스프링 데이터 JPA는 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트다.

CRUD를 처리하기 위한 공통 인터페이스를 제공한다.

리포지토리를 개발할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 스프링 빈으로 등록한다.

findByUserName 같은 메서드도 스프링 데이터 JPA가 메서드 이름을 분석해서 JPQL을 실행한다.

스프링 데이터 프로젝트

스프링 데이터 프로젝트는 다양한 데이터 저장소에 대한 접근을 추상화해서 반복적인 데이터 접근 코드를 줄여준다.

스프링 데이터 JPA는 그 중 하나로 JPA에 특화된 기능을 제공한다.

스프링 데이터 JPA 설정

책 541P 참고

리포지토리를 검색할 베이스 패키지 설정
라이브러리 설정 등

공통 인터페이스 기능

스프링 데이터 JPA는 CRUD를 공통으로 처리하는 JpaRepository 인터페이스를 제공한다.

public interface MemberRepository extends JpaRepository<Member, Long> {}

이렇게 <> 에 엔티티 클래스와 식별자 타입을 지정해주면 된다.

JpaRepository는 스프링 데이터의 Repository를 상속하는 CrudRepository를 상속하는 PagingAndSortingRepository를 상속한다.

주요 메서드

save() : 새로운 엔티티는 저장하고 이미 있는 엔티티는 수정한다.
엔티티에 식별자 값이 null이면 새로운 엔티티로 판단하여 persist를 호출하고 그렇지 않으면 merge를 호출한다.
필요하다면 스프링 데이터 JPA의 기능을 확장하여 판단 전략을 변경할 수 있다.

delete() : 엔티티를 삭제한다. remove를 호출한다.

findOne() : 엔티티 하나를 조회한다. find를 호출한다.

getOne() : 엔티티를 프록시로 조회한다. getReference를 호출한다.

findAll() : 모든 엔티티를 조회한다. 정렬이나 페이징 조건을 파라미터로 제공할 수 있다.

쿼리 메서드 기능

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

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

이 기능들을 활용하면 인터페이스만으로 필요한 대부분의 쿼리 기능을 개발할 수 있다.

메소드 이름으로 쿼리 생성

https://docs.spring.io/spring-data/jpa/docs/2.3.9.RELEASE/reference/html/#jpa.repositories

정해진 규칙을 사용해서 메소드 이름으로 쿼리를 생성할 수 있다.

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

JPA NamedQuery

쿼리에 이름을 부여해서 사용하는 방법이다.

스프링 데이터 JPA를 사용하지 않으면 어노테이션이나 XML에 쿼리를 정의하고 Named 쿼리를 JPA에서 직접호출 하려면 리포지토리에서 createNamedQuery를 사용해서 정의해줘야한다.

스프링 데이터 JPA를 사용하면 메소드 이름만으로 Named 쿼리를 호출할 수 있다.
만약 실행할 Named 쿼리가 없으면 메소드 이름으로 쿼리 생성 전략을 사용한다.(설정해서 기본 전략 변경가능)

@Query, 리포지토리 메소드에 쿼리 정의

실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리 느낌이다.
JPA Named 쿼리와 마찬가지로 애플리케이션 실행시점에 문법 오류를 발견할 수 있다.

메서드에 @Query 어노테이션으로 실행할 쿼리를 직접 작성한다.
nativeQuery = true 로 설정하면 네이티브 SQL을 사용할 수 있다.
이때 네이티브 SQL는 위치 기반 파라미터를 0부터 시작하고 JPQL은 1 부터 시작하므로 주의해야한다.

파라미터 바인딩

스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원한다.
기본 값은 위치 기반이다.
코드 가독성과 유지보수를 위해 이름 기반을 사용하는 것이 좋다.

벌크성 수정 쿼리

스프링 데이터 JPA에서 벌크성 수정, 삭제 쿼리는 Modifying 어노테이션을 사용하면 된다.

벌크성 쿼리 실행 이후 영속성 컨텍스트를 초기화 하고 싶으면 clearAutomatically = true 옵션을 주면 된다.

반환 타입

스프링 데이터 JPA는 결과가 한건 이상이면 컬렉션 인터페이스를 단건이면 반환 타입을 지정한다.

조회 결과가 없으면 컬렉션은 빈 컬렉션을 단건은 null을 반환한다.
단건을 기대하고 반환 타입을 지정했는데 결과가 2건 이상 조회되면 예외가 발생한다.

단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 getStringResult() 메서드를 호출한다.
조회 결과가 없으면 예외가 발생하는데 스프링 데이터 JPA는 이 예외를 무시하고 null을 반환한다.

페이징과 정렬

파라미터에 Pageable을 사용하면 반환 타입으로 List나 Page를 사용할 수 있다.
Page를 사용하면 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출한다.

PageRequest pageRequest =
	new PageRequest(원하는 페이지, 페이지 당 데이터 수, new Sort(정렬 방식, 정렬 기준));
    
Page<Member> result =
	memberRepository.findByNameStartingWith("김", pageRequest);
    
List<Member> members = result.getContent(); // 조회된 데이터
int totalPages = result.getTotalPages(); // 전체 페이지 수
boolean hasNextPage = result.hasNextPage(); // 다음 페이지 존재 여부

이외에도 page 인터페이스는 전체 데이터 수 등등의 메서드를 제공한다.

힌트

JPA 쿼리 힌트를 사용하려면 QueryHints 어노테이션을 사용하면 된다.
SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트다.

Lock

쿼리 식 락을 걸려면 Lock 어노테이션을 사용하면 된다.

명세

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

스프링 데이터 JPA는 필요한 메소드만 구현할 수 있는 방법을 제공한다.

직접 구현할 메서드를 위한 사용자 정의 인터페이스를 작성해야한다.
이때 인터페이스 이름은 상관없다.
이후 사용자 정의 인터페이스를 구현한 클래스를 작성해야한다.
이때 구현 클래스 이름은 리포지토리 인터페이스 이름 + Impl 로 작성해야 스프링 데이터 JPA가 사용자 정의 구현 클래스로 인식한다.

이후 리포지토리 인터페이스에서 사용자 정의 인터페이스와 JpaRepository를 상속 받으면 된다.

도메인 클래스 컨버터 기능

HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩 해준다.

그러나 이 기능을 통해 넘어온 엔티티를 컨트롤러에서 직접 수정해도 실제 데이터 베이스에는 방영되지 않는다.
OSIV를 사용하지 않으면 조회한 엔티티가 준영속 상태이므로 변경 감지기능이 동작하지 않는다.
수정한 내용을 반영하기 위해서 merge 해줘야한다.
OSIV를 사용하면 조회한 엔티티는 영속 상태이지만 OSIV 특성상 컨트롤러와 뷰에서는 영속성 컨텍스트를 플러시하지 않기때문에 수정한 내용을 데이터베이스에 반영하고 싶으면 트랜잭션을 시작하는 서비스 계층을 호출해야한다.

페이징과 정렬 기능

스프링 데이터 JPA가 사용하는 구현체

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

@Repository
@Transactional(readOnlt = true)
가 적용되어있다.

QueryDslPredicateExecutor

리포지토리 인터페이스에서 QueryDslPredicateExecutor를 상속받으면 QueryDSL을 사용할 수 있다.

QueryDslPredicateExecutor는 편리하게 QueryDSL을 사용할 수 있지만 join, fetch를 사용할 수 없다는 한계가 있다.

QueryDSL에서 제공하는 다양한 기능을 사용하려면 JPAQuery를 직접 사용하거나 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport를 사용해야한다.

QueryDslRepositorySupport

QueryDslRepositorySupport을 상속 받으면 조금 더 편리하게 QueryDSL을 사용할 수 있다.

profile
iha / ian / inho ha

0개의 댓글