JPA - 스프링 데이터 JPA

·2024년 5월 6일
0

Spring/JPA

목록 보기
11/15

인프런 김영한 강사님 강의를 듣고 정리한 내용입니다.

스프링 데이터 JPA

일반적으로 사용하게 되는 쿼리들(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를 했었다. 이 구현체에서도 이와 같은 방식으로 메서드들을 구현하고 있다. 그리고 그 외 부가기능을 덧붙여서 완성된 메서드들을 제공한다.

내부 어노테이션 - @Transactional

SimpleJpaRepository 내부에는 @Transactional 어노테이션이 붙어있다. JPA만 사용했을 때 서비스에서 @Transactional을 걸어서 사용했었다. 그래서 스프링 데이터 JPA 사용 시에는 리포지토리의 모든 연산이 트랜잭션 내에서 동작하게 된다.

내부 어노테이션 - @Repository

또한 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를 사용해 쿼리 생성하기

키워드를 사용해 메서드명으로 쿼리를 생성할 수도 있지만, 파라미터가 많아지거나 연산이 복잡해서 메서드명이 너무 길어진다면 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> 타입

메서드 반환 타입에 따라서 자동으로 타입을 결정해서 반환해준다.

profile
티스토리로 블로그 이전합니다. 최신 글들은 suhsein.tistory.com 에서 확인 가능합니다.

0개의 댓글