[Spring] 스프링 DB 2편 08

알재·2023년 12월 7일

스프링 DB

목록 보기
14/17

김영한님의 스프링 DB 2편 을 공부하여 정리한 글입니다.

스프링 데이터 JPA 예제와 트레이드 오프

클래스 의존 관계

런타임 객체 의존 관계

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

구조를 맞추기 위해서, 중간에 어댑터가 들어가면서 전체 구조가 너무 복잡해지고
사용하는 클래스도 많아지는 단점이 생겼다.

여기서 완전히 다른 선택을 할 수도 있다.
ItemService 코드를 일부 고쳐서 직접 스프링 데이터 JPA를 사용하는 방법이다.

클래스 의존 관계

런타임 객체 의존 관계

트레이드 오프

  • 이것이 바로 트레이드 오프다.
  • DI, OCP를 지키기 위해 어댑터를 도입하고, 더 많은 코드를 유지한다.
  • 어댑터를 제거하고 구조를 단순하게 가져가지만, DI, OCP를 포기하고, ItemService 코드를 직접 변경한다.

여기서 발생하는 트레이드 오프는 구조의 안정성 vs 단순한 구조와 개발의 편리성 사이의 선택이다.
어떤 상황에서는 구조의 안정성이 매우 중요하고, 어떤 상황에서는 단순한 것이 더 나은 선택일 수 있다.


실용적인 구조

복잡한 쿼리 분리

  • ItemRepositoryV2 는 스프링 데이터 JPA의 기능을 제공하는 리포지토리이다.
  • ItemQueryRepositoryV2 는 Querydsl을 사용해서 복잡한 쿼리 기능을 제공하는 리포지토리이다.
  • 기본 CRUD와 단순 조회는 스프링 데이터 JPA가 담당하고, 복잡한 조회 쿼리는 Querydsl이 담당하게 된다.

ItemRepository

public interface ItemRepositoryV2 extends JpaRepository<Item, Long> {
}
  • 스프링 데이터 JPA의 기능을 제공하는 리포지토리가 된다.
  • 기본 CRUD는 이 기능을 사용하면 된다.

ItemQueryRepository

@Repository
public class ItemQueryRepositoryV2 {

    private final JPAQueryFactory query;

    public ItemQueryRepositoryV2(EntityManager em) {
        this.query = new JPAQueryFactory(em);
    }

    public List<Item> findAll(ItemSearchCond cond) {
        return query
                .select(item)
                .from(item)
                .where(
                        likeItemName(cond.getItemName()),
                        maxPrice(cond.getMaxPrice())
                )
                .fetch();
    }

    private BooleanExpression likeItemName(String itemName) {
        if (StringUtils.hasText(itemName)) {
            return item.itemName.like("%" + itemName + "%");
        }
        return null;
    }

    private BooleanExpression maxPrice(Integer maxPrice) {
        if (maxPrice != null) {
            return item.price.loe(maxPrice);
        }
        return null;
    }
}
  • Querydsl을 사용한 쿼리 문제에 집중되어 있어서, 복잡한 쿼리는 이 부분만 유지보수 하면 되는 장점이 있다.

Service

@Service
@RequiredArgsConstructor
@Transactional
public class ItemServiceV2 implements ItemService {

    private final ItemRepositoryV2 itemRepositoryV2;
    private final ItemQueryRepositoryV2 itemQueryRepositoryV2;

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

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

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

    @Override
    public List<Item> findItems(ItemSearchCond itemSearch) {
        return itemQueryRepositoryV2.findAll(itemSearch);
    }
}
  • ItemServiceV2 는 ItemRepositoryV2 와 ItemQueryRepositoryV2 를 의존한다.

다양한 데이터 접근 기술 조합

트랜잭션 매니저 선택

  • JPA, 스프링 데이터 JPA, Querydsl은 모두 JPA 기술을 사용하는 것이기 때문에
    트랜잭션 매니저로 JpaTransactionManager 를 선택하면 된다.
  • 그런데 JdbcTemplate , MyBatis 와 같은 기술들은 내부에서 JDBC를 직접 사용하기 때문에 DataSourceTransactionManager 를 사용한다.

JPA와 JdbcTemplate 두 기술을 함께 사용하면 트랜잭션 매니저가 달라진다.

JpaTransactionManager의 다양한 지원

  • JpaTransactionManager 는 DataSourceTransactionManager 가 제공하는 기능도 대부분 제공한다.
  • JPA라는 기술도 결국 내부에서는 DataSource와 JDBC 커넥션을 사용하기 때문이다.
  • JpaTransactionManager 를 하나만 스프링 빈에 등록하면, JPA, JdbcTemplate, MyBatis
    모두를 하나의 트랜잭션으로 묶어서 사용할 수 있다.

profile
저장소

0개의 댓글