인프런 김영한 강사님 강의를 듣고 정리한 내용입니다.
일반적으로 사용하게 되는 쿼리들(findById, findAll 등)을 미리 제공한다. JpaRepository
를 상속하는 인터페이스를 만들면, 스프링 컨테이너를 띄울 때 구현체를 생성해 주입한다.
스프링 데이터 JPA를 사용하면 보편적인 CRUD와 관련된 쿼리들은 제공이 된다.
build.gradle
에 다음을 추가한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
스프링 데이터 JPA에서는 hibernate
를 구현체로 사용한다.
applicaiton.properties
에 다음과 같이 설정한다.
spring:
datasource:
url: # DB url
username: # DB username
password: # DB password
driver-class-name: # 사용하는 DB 드라이버 선택
jpa:
hibernate:
ddl-auto: create
# create로 하면 어플리케이션 실행 시 DB를 새로 생성함.(저장된 모든 데이터 삭제. 개발 시에만 사용)
properties:
hibernate:
# show_sql: true
format_sql: true
logging:
level:
org.hibernate.SQL: debug
# org.hibernate.type: trace
datasource에 관한 설정을 필수적으로 한다.
어플리케이션 실행 과정에서 SQL문을 확인하려면 format_sql과 logging에 관련된 설정을 해주면 된다.
스프링 데이터 JPA를 사용하면 구현체를 따로 만들어줄 필요 없이 interface만 생성하면 된다. 구현은 이미 되어 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
}
다음과 같이 리포지토리 인터페이스를 만들고, JpaRepository를 상속받도록 하면 된다. 첫번째 파라미터로는 엔티티를, 두번째 파라미터로는 엔티티 ID(pk)의 변수 타입을 지정하면 된다.
이제 일반적인 CRUD 쿼리들을 직접 정의하지 않고도 사용할 수 있다.
인터페이스를 구현하는 클래스를 만들 필요도 없다.
스프링 컨테이너를 띄울 때 구현체를 생성해 주입하게 되고, 이와 같은 방법으로 만드는 리포지토리는 @Repository 어노테이션을 따로 붙여줄 필요도 없다.
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID primaryKey);
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
// … more functionality omitted.
}
위와 같은 메서드들은 이미 구현이 되어있다.
리포지토리를 사용할 때는 리포지토리 인터페이스를 스프링으로부터 DI
받아서 그대로 사용하면 된다.
@RestController
@RequiredArgsConstructor
public class MemberController{
private final MemberRepository memberRepository;
// @RequiredArgsConstructor 의해 final 변수는 생성자 자동 생성. 생성자로부터 DI됨
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Long id){
Member member = memberRepository.findById(id).get();
return member.getUsername();
}
}
위와 같이 리포지토리를 전혀 구현하지 않고도 인터페이스만 생성하여 findById()라는 메서드를 사용했다.
JpaRepository 인터페이스를 상속하는 클래스를 확인해보면, SimpleJpaRepository
라는 이름의 클래스를 발견할 수 있다.
이 클래스에서 JpaRepository를 상속할 때 사용할 수 있는 메서드들을 확인할 수 있다.
기존에 스프링 데이터 JPA 없이 JPA만 사용할 때 EntityManager를 주입받아서 CRUD를 했었다. 이 구현체에서도 이와 같은 방식으로 메서드들을 구현하고 있다. 그리고 그 외 부가기능을 덧붙여서 완성된 메서드들을 제공한다.
SimpleJpaRepository 내부에는 @Transactional 어노테이션이 붙어있다. JPA만 사용했을 때 서비스에서 @Transactional을 걸어서 사용했었다. 그래서 스프링 데이터 JPA 사용 시에는 리포지토리의 모든 연산이 트랜잭션 내에서 동작하게 된다.
또한 SimpleJpaRepository 내부에는 @Repository 어노테이션이 붙어있다. 그래서 리포지토리 인터페이스에 @Repository 어노테이션을 붙일 필요가 없었던 것이다.
@Repository
어노테이션이 있으면 어플리케이션 실행 시 스프링 컨테이너에 빈으로 등록이 된다. 그리고 JPA에서 발생하는 예외들을 스프링에서 처리할 수 있는 예외들로 변환할 수 있게 된다.
스프링의 예외 추상화는 스프링에 상당히 중요하게 작용하며, 스프링의 동작 원리와도 관련이 있다.
기본적으로 스프링을 사용할 때 어떤 인터페이스의 구현체를 다른 구현체로 바꾸어도, 다른 비즈니스 로직을 고칠 필요 없이 DI 시켜서 잘 동작했다.
이때 예외가 공통화되어있지 않다면 구현체가 바뀔 때마다 예외 처리에 관한 부분도 수정해야했을 것이다.
하지만 스프링은 예외 추상화를 제공하여 구현체가 바뀌더라도 스프링의 예외로 변환해준다. 그래서 이처럼 편리하게 스프링을 사용할 수 있는 것이다.
일반적인 CRUD 외에 비교 연산이 들어가는 경우에는 쿼리를 따로 작성해야한다.
하지만 스프링 데이터 JPA를 사용하면 리포지토리 내에서 jpql를 직접 짜지 않고도 메서드명을 통해서 쿼리를 자동 생성할 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
// username과 age를 통해서 find
// 파라미터로 username과 age 주기
}
findBy 뒤에 오는 키워드
로 find 하는 쿼리를 작성해준다. (find와 By 사이에는 아무 단어나 넣어도 상관이 없다.)
카멜 케이스로 작성해야하고, 반드시 엔티티 내의 필드명과 똑같이 작성해야한다.
키워드는 https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query-methods.query-creation 를 확인하면 된다.
키워드를 사용해 메서드명으로 쿼리를 생성할 수도 있지만, 파라미터가 많아지거나 연산이 복잡해서 메서드명이 너무 길어진다면 JPQL로 쿼리를 직접 작성할 수도 있다.
쿼리를 직접 작성할 때는 @Query
어노테이션을 메서드 위에 붙여주면 된다.
public interface MemberRepository extends JpaRepository<Member, Long> {
// JPA Repository 인터페이스 내부에 @Query 를 사용해 바로 jpql 정의 가능
@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)
}
이때 JPQL 내부에서 사용되는 파라미터는 반드시 @Param
으로 특정해야 한다.
스프링 데이터 JPA를 사용하는 리포지토리 내에서 정의한 쿼리문은 application loading 시점에 파싱된다. (JPQL -> SQL)
한 번 파싱된 쿼리문은 어플리케이션이 동작하는 동안 정적쿼리
로 그대로 사용된다. 또한 로딩 시점에 파싱이 되기 때문에 중간에 오타가 나더라도 디버깅
이 가능하다.
스프링 데이터 JPA 사용 시 find 쿼리에 대한 반환 타입은 세 가지가 있다.
1. Entity 타입
2. Optional<Entity> 타입
3. List<Entity> 타입
메서드 반환 타입에 따라서 자동으로 타입을 결정해서 반환해준다.