JpaItemRepositoryV2

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.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

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();
        }
    }
}

 

 

의존관계와 구조

ItemService 는 ItemRepository 에 의존하기 때문에 ItemService 에서 SpringDataJpaItemRepository 를 그대로 사용할 수 없다.
 물론 ItemService 가 SpringDataJpaItemRepository 를 직접 사용하도록 코드를 고치면 되겠지만,  우리는 ItemService 코드의 변경없이 ItemService 가 ItemRepository 에 대한 의존을 유지하면서 DI를 통해 구현 기술을 변경하고 싶다.

 

 

 

여기서는 JpaItemRepositoryV2 가 MemberRepository 와 SpringDataJpaItemRepository 사이를 맞추기 위한 어댑터 처럼 사용된다.

 

 

 

 

클래스 의존 관계

 JpaItemRepositoryV2 는 ItemRepository 를 구현한다. 그리고 SpringDataJpaItemRepository 를 사용한다.

 

 

 

 

런타임 객체 의존 관계

 런타임의 객체 의존관계는 다음과 같이 동작한다.
 itemService jpaItemRepositoryV2 springDataJpaItemRepository(프록시 객체)

 

 

이렇게 중간에서 JpaItemRepository 가 어댑터 역할을 해준 덕분에 MemberService 가 사용하는 MemberRepository 인터페이스를 그대로 유지할 수 있고 클라이언트인 MemberService 의 코드를 변경하지 않아도 되는 장점이 있다.

 

 

 

 

다음으로 기능에 대해서 알아보자

 

 

 

save()

repository.save(item)
스프링 데이터 JPA가 제공하는 save() 를 호출한다.

 

 

 

update()

스프링 데이터 JPA가 제공하는 findById() 메서드를 사용해서 엔티티를 찾는다.
그리고 데이터를 수정한다.

이후 트랜잭션이 커밋될 때 변경 내용이 데이터베이스에 반영된다. (JPA가 제공하는 기능이다.)

 

 

 

 

findById()

repository.findById(itemId)
스프링 데이터 JPA가 제공하는 findById() 메서드를 사용해서 엔티티를 찾는다.

 

 

 

 

findAll()

데이터를 조건에 따라 4가지로 분류해서 검색한다.
 모든 데이터 조회
 이름 조회
 가격 조회
 이름 + 가격 조회

 

 

모든 조건에 부합할 때는 findByItemNameLikeAndPriceLessThanEqual() 를 사용해도 되고, repository.findItems() 를 사용해도 된다. 그런데 보는 것 처럼 조건이 2개만 되어도 이름이 너무 길어지는 단점이 있다. 따라서 스프링 데이터 JPA가 제공하는 메서드 이름으로 쿼리를 자동으로 만들어주는 기능과 @Query 로 직접 쿼리를 작성하는 기능 중에 적절한 선택이 필요하다.

 

 

추가로 코드를 잘 보면 동적 쿼리가 아니라 상황에 따라 각각 스프링 데이터 JPA의 메서드를 호출해서 상당히 비효율 적인 코드인 것을 알 수 있다. 앞서 이야기했듯이 스프링 데이터 JPA는 동적 쿼리 기능에 대한 지원이 매우 약하다. 이 부분은 이후에 Querydsl을 사용해서 개선해보자.

 

 

 

 

SpringDataJpaConfig

package hello.itemservice.config;

import hello.itemservice.repository.ItemRepository;

import hello.itemservice.repository.jpa.JpaItemRepositoryV2;
import hello.itemservice.repository.jpa.SpringDataJpaItemRepository;
import hello.itemservice.service.ItemService;
import hello.itemservice.service.ItemServiceV1;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;



@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);
    }
}

 SpringDataJpaItemRepository 는 스프링 데이터 JPA가 프록시 기술로 만들어주고 스프링 빈으로도 등록해준다.

 

 

 

 

테스트를 실행하자

먼저 ItemRepositoryTest 를 통해서 리포지토리가 정상 동작하는지 확인해보자. 테스트가 모두 성공해야 한다.

 

 

 

 

애플리케이션을 실행하자

ItemServiceApplication 를 실행해서 애플리케이션이 정상 동작하는지 확인해보자.

 

 

 

 

예외 변환


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

 

 

 

 

 

주의! - 하이버네이트 버그

하이버네이트 5.6.6 ~ 5.6.7 을 사용하면 Like 문장을 사용할 때 다음 예외가 발생한다.
스프링 부트 2.6.5 버전은 문제가 되는 하이버네이트 5.6.7을 사용한다.
java.lang.IllegalArgumentException: Parameter value [\] did not match expected type  [java.lang.String (n/a)]


build.gradle에 다음을 추가해서 하이버네이트 버전을 문제가 없는 5.6.5.Final 로 맞추자. ext["hibernate.version"] = "5.6.5.Final"

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

처 : 김영환 스프링 DB2 강의

'데이터 접근 기술 > Spring Data JPA' 카테고리의 다른 글

스프링 데이터 JPA 적용1  (0) 2022.08.20
스프링 데이터 JPA 주요 기능  (0) 2022.08.20
복사했습니다!