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

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

by hxxyeoniii 2024. 9. 1.

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

JpaItemRepositoryV2가 어댑터의 역할을 해주는 경우

 

 

(+) ItemService가 사용하는 ItemRepository 인터페이스는 그대로 유지할 수 있고, ItemService의 코드를 변경하지 않아도 됨

(+) DI, OCP 원칙을 지킬 수 있음 : ItemService를 변경하지 않고 ItemRepository의 구현체를 변경할 수 있음

 

(-) 중간에 어댑터가 들어가며 전체 구조가 복잡해지고 사용 클래스가 늘어남

(-) 어댑터 코드와 실제 코드까지 함께 유지보수 해야함

 

 

ItemService에서 직접 스프링 데이터 JPA를 사용하는 경우

 

=> DI, OCP를 포기하고 ItemService 코드를 직접 변경

=> 구조가 단순해지고 개발이 편리함

 

 

"트레이드 오프" : 구조의 안정성 vs 단순한 구조와 개발의 편리함

추상화에도 비용이 발생한다. 선택에 있어 정답이 있는 것은 아니지만, 프로젝트 상황에 맞는 더 적절한 선택지가 존재한다.

현재 상황에 맞는 선택을 하는 개발자가 좋은 개발자이다.


실용적인 구조

스프링 데이터 JPA의 기능을 최대한 살리며, Querydsl도 편리하게 사용할 수 있는 구조로 만들어보자.

 

 

ItemRepositoryV2.java

package hello.itemservice.repository.v2;

import hello.itemservice.domain.Item;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ItemRepositoryV2 extends JpaRepository<Item, Long> {
}

-> JpaRepository를 상속 받아 스프링 데이터 JPA의 기능을 제공하는 리포지토리가 됨

 

 

ItemQueryRepositoryV2.java

package hello.itemservice.repository.v2;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import hello.itemservice.domain.Item;
import hello.itemservice.domain.QItem;
import hello.itemservice.repository.ItemSearchCond;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import java.util.List;

import static hello.itemservice.domain.QItem.*;

@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을 사용한 쿼리 문제에 집중된 클래스로, 복잡한 쿼리는 이 부분만 유지보수하면 됨

 

 

ItemServiceV2.java

package hello.itemservice.service;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import hello.itemservice.repository.v2.ItemQueryRepositoryV2;
import hello.itemservice.repository.v2.ItemRepositoryV2;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

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

@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 cond) {
        return itemQueryRepositoryV2.findAll(cond);
    }
}

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

트랜잭션 메니저 선택

JPA, 스프링 데이터 JPA, Querydsl은 모두 JPA 기술을 사용하기 때문에 트랜잭션 매니저로 'JpaTransactionManager'를 선택하면 된다. 그러면 스프링 부트는 자동으로 JpaTransactionManager를 스프링 빈에 등록한다.

그런데 JdbcTemplate, Mybatis와 같은 기술들은 내부에서 JDBC를 직접 사용하기 때문에 'DataSourceTransactionManager'를 사용한다.

-> JPA와 JdbcTemplate 두 기술을 함께 사용하면 트랜잭션 매니저가 달라진다. 하지만 걱정할 필요 없음

 

 

JpaTransactionManager의 다양한 지원

JpaTransactionManager는 DataSourceTransactionManager가 제공하는 기능을 대부분 제공한다.

결국 내부에 DataSource와 JDBC 커넥션을 사용하기 때문

-> JpaTransactionManager를 스프링 빈에 등록하면 모두 하나의 트랜잭션으로 묶어서 사용할 수 있음

 

 

주의점

JPA의 플러시 타이밍에 주의해야 함

JPA는 데이터를 변경하면 변경 사항을 즉시 DB에 반영하지 않고 트랜잭션이 커밋되는 시점에 변경사항을 DB에 반영한다.

하나의 트랜잭션 안에서 JPA를 통해 데이터 변경 후 JdbcTemplate을 호출하는 경우, JdbcTemplate에서는 JPA가 변경한 데이터를 읽지 못하는 문제가 발생한다.

따라서 JPA 호출이 끝난 시점에 JPA가 제공하는 플러시 기능을 사용해 변경 내역을 DB에 반영해주어야 한다.