[JPA] 스프링 Data JPA 기술을 공부하며 배운 내용 정리

해당 게시글에는 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard

 

스프링 DB 2편 - 데이터 접근 활용 기술 강의 | 김영한 - 인프런

김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔드

www.inflearn.com

섹션 6 데이터 접근 기술 - 스프링 데이터 JPA을 수강하며 알게된 점들을 정리한 내용이 담겨있습니다.

1. 스프링 Data란?

https://spring.io/projects/spring-data

 

Spring Data

Spring Data’s mission is to provide a familiar and consistent, Spring-based programming model for data access while still retaining the special traits of the underlying data store. It makes it easy to use data access technologies, relational and non-rela

spring.io

It makes it easy to use data access technologies, relational and non-relational databases, map-reduce frameworks, and cloud-based data services.

 

우리는 MySQL 뿐만 아니라 Mongo, Redis 등 같이 여러 형태의 DB와 접근해야 할 수 있다.

이들의 형태는 조금씩 다를 수 있지만 어플리케이션과 상호작용함에 있어서 결국 CRUD를 한다는 건 동일하다.

따라서 스프링은 DB와 접근하는 기술을 구현체에 종속시키지 않고, 스프링과 호환되는 데이터 접근 기술을 제공하기 위해

Spring Data라는 기술을 만들었다.

etc-image-0
https://spring.io/projects/spring-data

스프링 Data는 여러 모듈을 지원하고 있으며, 이 중에서 주로 사용하게 될 Spring Data JPA는 JPA라는 자바 ORM 표준 기술을 스프링 진영에서 보다 쉽게 사용할 수 있도록 다양한 인터페이스 및 구현체 등을 제공한다.

 

2. 스프링 Data JPA

우리는 보통 JPA를 사용할 때, Repository를 클래스가 아닌 인터페이스로 생성한다. 다음과 같이 말이다.

public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
}

 

이 때, 우리는 JpaRepository를 상속받은 인터페이스를 생성하면 해당 인터페이스를 통해 JPA 로직을 쉽게 호출할 수 있다. 먼저, 상속받은 JpaRepository는 또한 여러 인터페이스를 상속받고 있다.

 

etc-image-1
https://www.geeksforgeeks.org/spring-boot-difference-between-crudrepository-and-jparepository/

JPA 기술을 통해 DB 관련 로직을 호출할 때, 주로 사용하는 save, findById, delete, count 등의 메서드는 CrudRepository에 구현되어 있다. CrudRepository 인터페이스에 직접 들어가보면 쉽게 확인할 수 있다. 대부분 우리가 사용하는 메서드들이 이미 CrudRepository에 구현되어 있기에 JpaRepository가 아닌 CrudRepository를 구현하여 사용하는 경우도 간혹 보인다.

 

어쨌든 우리는 위 그림에 있는 인터페이스를 구현한 구현체가 아니라 상속받은 인터페이스를 생성하기만 한다.

이것이 가능한 이유는 뭘까? 이는 바로 Spring Data JPA가 지원하는 동적 프록시 기술이다.

아직 동적 프록시에 대해 정확하게 알지는 못하지만 앞서 @Repository를 붙였을 때, 스프링 예외로 추상화하는 프록시 객체가 생성됨을 알 수 있었다. 마찬가지로 Spring Data JPA 또한 JpaRepository를 상속받은 인터페이스에 대한 구현체를

자동으로 생성한다.

etc-image-2
Repository를 주입받는 서비스 계층에서 클래스를 로그로 출력한 결과
etc-image-3

Spring Data JPA가 프록시 구현체를 알아서 생성하기 때문에 우리는 구현체를 직접 생성하지 않아도 되는 것이다.

(프록시 구현체가 생성되기 때문에 @Repository를 붙이지 않아도, 스프링 예외 추상화까지 해준다!)

 

3. 스프링 Data JPA가 제공하는 기술, 쿼리 메소드

스프링 Data JPA를 사용하면서 가장 헷갈렸던게 바로 이 쿼리 메소드다. 개발자가 직접 만들어야 되는게 있고, 안 만들어도 되는게 있다고 하는데 그 경계를 명확히 알지 못했다.

 

먼저, 앞서 살펴봤듯이 엔티티에 대한 CRUD 기능은 제공한다. 다만, 엔티티가 갖고 있는 필드를 조건으로 하는 CRUD는

스프링 Data JPA에서 자동으로 지원하지 않는다. 이는 비즈니스 영역에 해당하기에 스프링 Data JPA가 모르는게 당연하다.

 

즉, 구체적인 조건이 추가되는 경우 JpaRepository를 상속받은 인터페이스에 메서드를 생성해줘야 하는데, 이 때 편리한 점은 메서드 시그니처만 만들고, 실제 구현부는 작성하지 않아도 되는 것이 가장 큰 장점이다.

 

Item이라는 엔티티가 itemName, price라는 필드(DB에서는 컬럼)를 갖고 있을 때, 해당 필드를 조건으로 조회하는 쿼리문이 필요하다면 어떻게 할까?

 

https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html

 

JPA Query Methods :: Spring Data JPA

By default, Spring Data JPA uses position-based parameter binding, as described in all the preceding examples. This makes query methods a little error-prone when refactoring regarding the parameter position. To solve this issue, you can use @Param annotati

docs.spring.io

etc-image-4
컨벤션 / 예시 / 예시에 대한 JPQL

사실 공식문서에 다 나와있다. 쿼리 메소드 컨벤션과 함께 대응되는 JPQL도 같이 나와있다. 우리는 해당 표를 보고

"아 메서드를 이렇게 작성하면 이런 JPQL이 생성되는구나...!"라고 이해하면 된다.

public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {

    List<Item> findByItemNameLike(String itemName);

    List<Item> findByPriceLessThanEqual(Integer price);

    //쿼리 메서드 : 메서드 이름이 길어지는 문제가 발생!
    List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);
}

 

사실 조건이 하나 혹은 두개가 붙은 경우엔 메서드 이름이 그리 길지 않을 수 있다.

하지만 세 번째 메서드처럼 두 개의 조건에 대한 네이밍이 길어질 경우, 메서드 이름이 매우 길어질 수 있다.

이럴 때는 개발자가 직접 JPQL을 작성하여 스프링 Data JPA의 쿼리 메서드 컨벤션을 따르지 않는 방법을 선택하면 된다.

 

//쿼리 메서드 : 메서드 이름이 길어지는 문제가 발생!
List<Item> findByItemNameLikeAndPriceLessThanEqual(String itemName, Integer price);

//쿼리 직접 실행
@Query("select i from Item i where i.itemName like :itemName and i.price <= :price")
List<Item> findItems(@Param("itemName") String itemName, @Param("price") Integer price);

 

정리하자면 다음과 같다. 자동으로 할 것이냐 수동으로 할 것이냐 이 차이다.

  1. 스프링 Data JPA가 지원하는 형식으로 메소드 이름을 작성할 경우 : 메서드 이름에 맞게 JPQL 자동 생성
  2. 스프링 Data JPA가 지원하는 메서드 이름을 사용하지 않을 경우 : 사용자가 직접 작성한 JPQL을 생성

2번 방식, 수동을 선택할 경우, @Query와 해당 JPQL에 바인딩되는 값을 @Param으로 선언해야 한다.

 

참고로 LIKE 절이 들어가는 쿼리문의 경우, 바인딩되는 값에 %도 같이 넣어줘야 한다.

// Repository에서 생성한 쿼리 메소드
List<Item> findByItemNameLike(String itemName);

// 해당 메서드를 사용하는 곳
springDataJpaItemRepository.findByItemNameLike("%" + itemName + "%");