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.lang.ref.PhantomReference;
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.findItems(itemName, maxPrice);
} else if (StringUtils.hasText(itemName)) {
return repository.findByItemNameLike(itemName);
} else if (maxPrice != null) {
repository.findByPriceLessThanEqual(maxPrice);
} else {
return repository.findAll();
}
return null;
}
}
의존관계와 구조
조금 복잡하지만, 새로운 리포지토리를 만들어서 이 문제를 해결해보자.
여기서는 JpaItemRepositoryV2 가 ItemRepository 와 SpringDataJpaItemRepository 사이를 맞추기 위한 어댑터 처럼 사용된다.


이렇게 중간에서 JpaItemRepository 가 어댑터 역할을 해준 덕분에 ItemService 가 사용하는 ItemRepository 인터페이스를 그대로 유지할 수 있고 클라이언트인 ItemService 의 코드를 변경하지 않아도 되는 장점이 있다.
save()
repository.save(item)
스프링 데이터 JPA가 제공하는 save() 를 호출한다.
update()
스프링 데이터 JPA가 제공하는 findById() 메서드를 사용해서 엔티티를 찾는다. 그리고 데이터를 수정한다.
이후 트랜잭션이 커밋될 때 변경 내용이 데이터베이스에 반영된다. (JPA가 제공하는 기능이다.)
findById()
repository.findById(itemId)
스프링 데이터 JPA가 제공하는 findById() 메서드를 사용해서 엔티티를 찾는다.
findAll()
데이터를 조건에 따라 4가지로 분류해서 검색한다.
모든 조건에 부합할 때는 findByItemNameLikeAndPriceLessThanEqual() 를 사용해도 되고,
repository.findItems() 를 사용해도 된다. 그런데 보는 것 처럼 조건이 2개만 되어도 이름이 너무 길어지는 단점이 있다.
따라서 스프링 데이터 JPA가 제공하는 메서드 이름으로 쿼리를 자동으로
만들어주는 기능과 @Query 로 직접 쿼리를 작성하는 기능 중에 적절한 선택이 필요하다.
기능을 모두 개발했으니, 설정하고 실행해보자.
SpringDataJpaConfig
package hello.itemservice.config;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.jpa.JpaItemRepository;
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;
import javax.persistence.EntityManager;
@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);
}
}
ItemServiceApplication - 변경
//@Import(JpaConfig.class)
@Import(SpringDataJpaConfig.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {}
테스트를 실행하자
먼저 ItemRepositoryTest 를 통해서 리포지토리가 정상 동작하는지 확인해보자. 테스트가 모두 성공해야 한다.
애플리케이션을 실행하자
ItemServiceApplication 를 실행해서 애플리케이션이 정상 동작하는지 확인해보자.
예외 변환
스프링 데이터 JPA도 스프링 예외 추상화를 지원한다. 스프링 데이터 JPA가 만들어주는 프록시에서 이미 예외 변환을 처리하기 때문에, @Repository 와 관계없이 예외가 변환된다.
정리
스프링 데이터 JPA의 대표적인 기능을 알아보았다. 스프링 데이터 JPA는 이 외에도 정말 수 많은 편리한 기능을 제공한다. 심지어 우리가 어렵게 사용하는 페이징을 위한 기능들도 제공한다. 스프링 데이터 JPA는 단순히 편리함을 넘어서 많은 개발자들이 똑같은 코드로 중복 개발하는 부분을 개선해준다. 개인적으로 스프링 데이터 JPA는 실무에서 기본으로 선택하는 기술이다.