Spring Data JPA는 JPA랑 다른건가? Spring Data JPA가 무엇이고, 왜 탄생했는지를 시작해서 어떻게 사용하면 좋을지에 대한 포스팅을 작성하기로 했다.
최악의 EJB
아주 예전에 EJB(Enterprise Java Beans)라는 기술이 있었다. 자바 표준으로 제공하는 기술이었는데 이 기술을 사용하려면 정말 너무너무 어렵고 복잡하고 코드도 지저분하고 말도 아니었다. 그래서 사용자들은 피로감을 느꼈고, 오죽하면 순수한 자바로 다시 돌아가자라는 의미를 가진 POJO(Plan Old Java Object)라는 단어도 탄생할 지경이었다.
그래서 EJB의 지옥에서 탈출하기 위해 두명의 영웅이 탄생한다. 스프링의 창시자라고도 불리는 로드 존슨과 하이버네이트의 창시자 개빈 킹. 개빈 킹이라는 사람은 EJB의 엔티티빈을 사용하면서 데이터 접근 기술을 사용하려다가 "도저히 못해먹겠다. 이렇게 할 바에 내가 만드는 게 더 나을것 같다"라는 생각을 가지고 만든 하이버네이트가 곧 JPA라는 표준을 만들게 된다. 그러니까 역사적으로도 JPA라는 표준 기술보다 하이버네이트가 먼저 나왔다. 사람들이 하이버네이트에 열광을 하니 자바 진영에서 개빈 킹이라는 사람을 불러다가 이 기술을 표준화해서 자바 표준으로 하나 만들고 싶다라는 청을 했고 그렇게 만들어진게 JPA다.
그렇게 JPA라는 표준과 그 표준을 구현한 구현체 중 하나인 Hibernate가 있게 된다. 오픈소스를 기반으로 표준을 만들어 냈기 때문에 실용적일뿐더러 딱딱하고 고리타분한 표준이 아니라 사용자들이 훨씬 더 사용하기 쉽게 표준이 만들어졌다.
Spring Data JPA의 탄생
그래서 이렇게 스프링과 JPA가 한 묶음이 되어 사람들이 편리하게 개발을 하던 중 이런 고민이 생기게 된다. MongoDB, Redis, 관계형 데 이터베이스 등 데이터 접근 기술이 하도 방대하고 방식도 다른데 결국 컨셉은 데이터를 어딘가에 저장하고 그 데이터를 관리, 보관, 사용에 목적이 있지 않나? 그럼 이 데이터 접근 기술의 방식은 다를지언정 결국 CRUD라는 큰 컨셉이 동일하다 보니 이 또한 표준으로 만들어볼까? 라는 생각으로 만들어진 것이 바로 Spring Data 라는 표준이다. 그리고 그 표준을 따라 구현한 하나의 구현체가 Spring Data JPA가 있는 것이다. Spring Data Redis, Spring Data Mongo 등 여러가지 구현체가 있지만 결국 Spring Data라는 큰 표준을 두고 그 표준에 맞춰 구현 기술을 구현하되 각각의 특징에 맞게 구현된 것이다.
그리고 우리는 여기서 Spring Data JPA를 배울것이고 이는 JPA를 사용한 방식인 것이다.
이 Spring Data 표준은 결국 이런 것이다.
- CRUD + 쿼리
- 동일한 인터페이스
- 페이징 처리
- 메서드 이름으로 쿼리 생성
- 스프링 MVC에서 ID값만 넘겨도 도메인 클래스로 바인딩
이게 다 결국 데이터 접근 기술들이 가지고 있는 특징들 아닌가? 그 안에 방식이 살짝 다를뿐 결국 목적은 여기에 있다. 그러다보니 이런 표준을 만들고 구현은 너네 입맛에 맞게 구현해라라고 표준이 만들어졌다.
Spring Data JPA 주요 기능
스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 라이브러리이다. 수많은 편리한 기능을 제공하지만, 가장 대표적인 기능은 다음과 같다.
- 공통 인터페이스 기능
- 쿼리 메서드 기능
공통 인터페이스 기능
- JpaRepository 인터페이스를 통해서 기본적인 공통 CRUD 기능을 제공한다.
- 공통화 가능한 기능이 거의 모두 포함되어 있다.
- CrudRepository에서 지금은 findOne() → findById()로 변경되었다.
JpaRepository를 사용하는 방법은 정말 매우 간단한데, 그냥 아래와 같이 인터페이스를 상속만 받으면 된다.
public interface ItemRepository extends JpaRepository<Item, Long> {}
- JpaRepository 인터페이스를 상속 받고, 제네릭에 관리할 <엔티티, 엔티티ID 타입>을 주면 된다.
- 그러면 JpaRepository가 제공하는 기본 CRUD 기능을 모두 사용할 수 있다.
- 어떻게 인터페이스만 상속받으면 위 기능을 다 사용할 수 있냐? 당연히 스프링 데이터 JPA 라이브러리에서 구현 클래스를 대신 만들어준다. 스프링을 사용하다보면 느끼겠지만 이런 유사한 기능이 정말 많다.
- JpaRepository 인터페이스만 상속받으면, 스프링 데이터 JPA가 프록시 기술을 사용해서 구현 클래스를 만들어준다.
- 그리고 만든 구현 클래스의 인스턴스를 만들어서 스프링 빈으로 등록한다.
- 따라서 개발자는 구현 클래스 없이 인터페이스만 만들면 기본 CRUD 기능을 사용할 수 있다.
쿼리 메서드 기능
스프링 데이터 JPA는 인터페이스에 메서드만 적어두면, 메서드 이름을 분석해서 쿼리를 자동으로 만들어주고 실행해주는 기능을 제공한다. 다음 코드를 보자.
순수한 JPA 레포지토리
public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
return em.createQuery("select m from Member m where m.username = :username and m.age > :age")
.setParameter("username", username)
.setParameter("age", age).getResultList();
}
- 순수하게 JPA만 사용할 경우에, 개발자는 이러한 JPQL을 작성해서 메서드로 만들어 두어야 한다.
스프링 데이터 JPA
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
- 이렇게 메서드 시그니처만 만들어두면, 스프링 데이터 JPA는 메서드 이름을 분석해서 필요한 JPQL을 만들고 실행해준다. 이게 바로 위에 코드랑 완전히 동일하게 만들어준다.
- 물론, JPQL은 JPA가 SQL로 번역해서 실행된다. 이건 당연한 이야기다.
위와 같이 메서드 시그니처만 만들어두면 되는데 이것도 규칙이라는 게 있다.
- 조회: findBy, readBy, queryBy, getBy 와 같은 키워드를 사용해야 한다.
- COUNT: countBy
- EXISTS: existsBy
- 삭제: deleteBy, removeBy
이보다도 내용이 더 많은데 이 부분은 공식 문서를 참고해서 더 찾아볼 수 있다.
JPQL 직접 사용하기
물론 위와 같이 메서드 시그니처만 만들어도 알아서 뚝딱 뚝딱 만들어주는데 가끔은 그게 불편할때가 있다. 조건이 너무 많아서 메서드 이름이 너무 길어지는 경우가 딱 이 경우이다. 그럴땐 JPQL을 직접 작성할 수 있도록 해준다.
public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
//쿼리 메서드 기능
List<Item> findByItemNameLike(String itemName);
//쿼리 직접 실행
@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);
}
- 위 코드와 같이 @Query 애노테이션을 사용해서 직접 JPQL을 작성하면 이 메서드를 실행하면 작성한 JPQL대로 쿼리를 날려준다.
- 참고로, 스프링 데이터 JPA는 JPQL 뿐만 아니라, JPA의 네이티브 쿼리도 지원해준다. 그러니까 JPQL말고 SQL을 직접 작성해서 실행하게도 해준다.
스프링 데이터 JPA 적용
라이브러리를 먼저 추가하자.
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
SpringDataJpaItemRepository
package hello.itemservice.repository.jpa;
import hello.itemservice.domain.Item;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface SpringDataJpaItemRepository extends JpaRepository<Item, Long> {
List<Item> findByItemNameLike(String itemName);
List<Item> findByPriceLessThanEqual(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);
}
- 스프링 데이터 JPA가 제공하는 JpaRepository 인터페이스를 상속받으면 기본적인 CRUD 기능을 사용할 수 있다.
- 그런데 이름으로 검색하거나, 가격으로 검색하는 기능은 공통으로 제공할 수 있는 기능이 아니다. 따라서 쿼리 메서드 기능을 사용하거나 @Query 애노테이션을 사용해서 직접 쿼리를 실행하면 된다.
- 아쉽게도 스프링 데이터 JPA는 동적 쿼리에 약하다. 마치 JdbcTemplate처럼. 그래서 이후에 QueryDsl을 사용하여 이 부분을 깔끔하게 해결해 볼 생각이다.
스프링 데이터 JPA의 스프링 데이터 접근 예외 추상화 지원
참고로, 이렇게 스프링 데이터 JPA가 제공하는 JpaRepository 인터페이스를 상속받으면 구현체도 프록시로 자동으로 만들어 준다고 했는데 그때, 스프링 예외 추상화를 지원한다. 즉, 스프링 데이터 JPA가 만들어주는 프록시에서 이미 예외 변환을 처리해서 구현체로 빈을 등록해주기 때문에 @Repository 애노테이션이 굳이 없어도 예외가 발생 시 스프링 데이터 접근 예외가 반환된다.
스프링 데이터 접근 예외 추상화는 스프링에서 제공해주는 기능이다. 결국 JPA를 사용하던, JDBC 기술을 직접 사용하던, 결국은 내부에서 JDBC 기술을 사용해서 데이터베이스와 연동한다. 그 과정에서 에러가 발생하면 SQLException이 발생하는데, 이 예외를 스프링은 추상화하여 예외 계층을 지원해준다. 그 부분에 대한 얘기인데, 이 내용은 다음 포스팅에 자세히 작성해 두었다.
'Spring + Database' 카테고리의 다른 글
[Renewal] 스프링 트랜잭션 이해 (0) | 2024.12.08 |
---|---|
[Renewal] 데이터 접근 기술에 대한 고민 두가지 (2) | 2024.12.08 |
[Renewal] MyBatis (4) | 2024.12.06 |
[Renewal] JdbcTemplate (8) | 2024.12.06 |
[Renewal] 테스트 시 데이터베이스 연동 (0) | 2024.12.05 |