본문 바로가기
인프런/스프링 DB 2편 - 데이터 접근 활용 기술

[인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 6. 데이터 접근 기술 - 스프링 데이터 JPA

by hxxyeoniii 2024. 8. 11.

스프링 데이터 JPA 주요 기능

스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 라이브러리

 

 

1. 공통 인터페이스 기능

-> JpaRepository 인터페이스를 통해 기본적 CRUD 기능을 제공

-> 공통화 가능한 기능이 거의 모두 포함되어 있음

 

 

JpaRepository

 

 
사용 예시
 
public interface ItemRepository extends JpaRepository<Item, Long> {
}
 
-> JpaRepository 인터페이스를 상속 받고, 제네릭에 관리할 <엔티티, 엔티티 ID>를 주면 JpaRepository가 제공하는 기본 CRUD 기능을 모두 사용할 수 있다.
 
 
 
스프링 데이터 JPA가 구현 클래스를 대신 생성

-> JpaRepository 인터페이스만 상속받으면 스프링 데이터 JPA가 프록시 기술을 사용해 구현 클래스를 만들어준다. 그리고 만든 구현 클래스의 인스턴스를 만들어 스프링 빈으로 등록한다.

-> 개발자는 구현 클래스 없이 인터페이스만 만들면 기본 CRUD 기능을 사용할 수 있다.

 

 

2. 쿼리 메서드 기능

 

순수 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

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}

-> 스프링 데이터 JPA는 메서드 이름을 분석해 필요한 JPQL을 만들고 실행해준다.

 

 

* 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능

1. 조회 : find..By, read..By, query..By, get..By

2. COUNT : count..By -> 반환타입 long

3. EXISTS : exists..By -> 반환타입 boolean

4. 삭제 : delete..By, remove..By -> 반환타입 long

5. DISTINCT : findDistinct, findMemberDistinctBy

6. LIMIT : findFirst, findTop, findTop3

 

 

JPQL 직접 사용

: 쿼리 메서드 기능 대신 직접 JPQL을 사용하고 싶을 때는 @Query와 함께 JPQL을 작성하면 된다. 이때는 메서드 이름으로 실행되는 규칙은 무시된다.

: JPQL뿐 아니라 JPA의 네이티브 쿼리 기능도 지원하는데, JPQL 대신 SQL을 직접 작성할 수도 있다.


스프링 데이터 JPA 적용

build.gradle 추가

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

-> JPA, 하이버네이트, 스프링 데이터 JPA, 스프링 JDBC 관련 기능이 모두 포함되어 있음

 

 

SpringDataJpaItemRepository.java

ItemService의 코드 변경 없이 ItemService가 ItemRepository에 대한 의존을 유지하며 DI를 변경하기 위해,

JpaItemRepositoryV2가 ItemRepository와 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> {

    // select i from Item i where i.name like ?
    List<Item> findByItemNameLike(String itemName);

    // select i from Item i where i.price <= ?`
    List<Item> findByPriceLessThanEqual(Integer price);

    // 쿼리 메서드(아래와 같은 기능)
    // select i from Item i where i.itemName like ? and i.price <= ?
    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);
}

-> 메서드 이름으로 쿼리를 실행하는 기능은 간단한 경우 유용하지만, 복잡해지면 직접 JPQL 쿼리를 작성하는 것이 좋음

 

 

JpaItemRepositoryV2.java

package hello.itemservice.repository.jpa;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

@Repository
@Transactional
@RequiredArgsConstructor
public class JpaItemRepositoryV2 implements ItemRepository {

    private final SpringDataJpaItemRepository repository;

    @Override
    public Item save(Item item) {
        return repository.save(item);
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        Item findItem = repository.findById(itemId).orElseThrow();
        findItem.setItemName(updateParam.getItemName());
        findItem.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
    }

    @Override
    public Optional<Item> findById(Long id) {
        return repository.findById(id);
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();

        if(StringUtils.hasText(itemName) && maxPrice != null) {
            // 두가지 조건이 모두 있을 경우
            // return repository.findByItemNameLikeAndPriceLessThanEqual(itemName, maxPrice);
            return repository.findItems(itemName, maxPrice);
        } else if(StringUtils.hasText(itemName)) {
            return repository.findByItemNameLike(itemName);
        } else if(maxPrice != null) {
            return repository.findByPriceLessThanEqual(maxPrice)
        } else {
            return repository.findAll();
        }
    }
}

 

 

-> JpaItemRepository가 어댑터 역할을 해준 덕분에 ItmService의 코드 변경하지 않아도 됨

 

 

SpringDataJpaConfig.java

@Configuration
@RequiredArgsConstructor
public class SpringDataJpaConfig {

    private final SpringDataJpaItemRepository springDataJpaItemRepository;

    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }
    
    @Bean
    public ItemRepository itemRepository() {
        return new JpaItemRepositoryV2(springDataJpaItemRepository);
    }   
}

 

 

* 예외 변환

스프링 데이터 JPA도 스프링 예외 추상화를 지원한다.

스프링 데이터 JPA가 만들어주는 프록시에서 이미 예외 변환을 처리하기 때문에, @Repository와 관계없이 예외가 변환된다.