본문 바로가기

좌충우돌 개발기!/DB 설정

[Spring Boot] (5)다중 DB 환경에서 QueryDSL 사용하기

서로 다른 DB를 사용하는 Service에서 QueryDSL을 사용하여 조회 메서드를 만든 후 테스트를 해보았다.

@Service
@RequiredArgsConstructor
public class PokemonService {

  private final JPAQueryFactory queryFactory;

  public List<PokemonEntity> selectPokemon() {
    return queryFactory
      .selectFrom(pokemonEntity)
      .fetch();
  }

}
@Service
@RequiredArgsConstructor
public class ItemService {

  private final JPAQueryFactory queryFactory;

  public List<ItemEntity> selectItems() {

    return queryFactory
      .selectFrom(itemEntity)
      .fetch();
  }
  @Autowired
  PokemonService pokemonService;

  @Autowired
  ItemService itemService;

@Test
void firstDB() {
  List<PokemonEntity> pokemonEntities = pokemonService.selectPokemon();
  pokemonEntities.forEach(ent -> System.out.println("FIRST DB QueryDSL : " + ent.getPokemonId() + " : " + ent.getName()));
}

@Test
void secondDB() {
  List<ItemEntity> itemEntities = itemService.selectItems();
  itemEntities.forEach(ent -> System.out.println("SECOND DB QueryDSL : " + ent.getPrice() + ", " + ent.getStockQuantity()));
}

 

 

 

테스트를 돌려보니 firstDB는 성공했지만 secondDB는 실패했다. 왜일까?

 

 

 

디버깅을 해보면 JPAQueryFactory에 첫 번째 DB의 정보가 셋팅된 EntityManager가 들어간 것을 알 수 있다.

 

 

스프링은 싱글톤으로 Bean을 생성하기 때문에 다중 DB를 사용할 경우, 어떤 DB 설정을 선택해야할 지 모른다.

때문에 주로 사용하는 DB의 설정에 @Primary 어노테이션을 사용하여 우선적으로 사용하게끔 한다.

따라서 별도로 지정해주지 않는 이상, @Primary가 붙은 DB 설정의 EntityManager를 사용한다.

 

 

JPAQueryFactory에서 DB설정을 별도로 사용하기 위해서 unitName을 이용해 각각 EntityManager를 만든다.

각 EntityManager를 주입한 JPAQueryFactory에 @Qualifier로 이름을 붙여준다.

이후 해당 @Qualifier를 사용하여 JPAQueryFactory를 주입받아 사용하면 되는데, @Primary를 붙여주면 @Qualifier를 사용하지 않을 경우 해당 JPAQueryFactory를 주입하게 된다.

 

기존

@Configuration
public class JpaConfig {

  @Bean
  JPAQueryFactory jpaQueryFactory(EntityManager em) {
    return new JPAQueryFactory(em);
  }

}

 

 

변경

@Configuration
public class JpaConfig {
  
  @PersistenceContext(unitName = "firstEntityManager")
  private EntityManager firstEntityManager;

  @PersistenceContext(unitName = "secondEntityManager")
  private EntityManager secondEntityManager;

  @Bean
  @Primary
  @Qualifier("FirstQF")
  JPAQueryFactory firstQueryFactory() {
    return new JPAQueryFactory(firstEntityManager);
  }

  @Bean
  @Qualifier("SecondQF")
  JPAQueryFactory secondQueryFactory() {
    return new JPAQueryFactory(secondEntityManager);
  }

}

 

 

사용 시에는 다음과 같이 생성자를 만들어 사용하면 된다.

그런데 Lombok을 사용하면 될 걸 사용하는 곳마다 일일이 생성자 코드를 만들어 줘야할까?

@Service
public class ItemService {

  private final JPAQueryFactory secondQueryFactory;

  public ItemService(@Qualifier("SecondQF") JPAQueryFactory secondQueryFactory) {
    this.secondQueryFactory = secondQueryFactory;
  }

  public List<ItemEntity> selectItems() {

    return secondQueryFactory
      .selectFrom(itemEntity)
      .fetch();
  }

}

 

 

그래서 코드를 이렇게 바꾸어 보니

@Service
@RequiredArgsConstructor
public class ItemService {

  @Qualifier("SecondQF")
  private final JPAQueryFactory queryFactory;

  public List<ItemEntity> selectItems() {

    return queryFactory
      .selectFrom(itemEntity)
      .fetch();
  }

}

 

 

Lombok은 생성자를 만들 때 @Qualifier을 복사하지 않는다고 한다.

 

 

class파일을 확인해보니 정말 @Qualifier가 없는 것을 볼 수 있었다. 이러면 @Primary가 붙은 JPAQueryFactory가 주입이 되어버릴 것이다. 그럼 정말 Lombok을 사용하여 생성자를 만들 수는 없는 것일까?

그러기 위해서 Lombok 설정을 추가해주면 된다.

 

 

src/main/java 폴더 아래에 lombok.config 파일을 만든다.

그 후,  아래 설정을 추가해준다.

lombok.copyableannotations += org.springframework.beans.factory.annotation.Qualifier

 

 

 

설정을 추가하고 나면 더이상 경고가 뜨지 않는다.

다시 build를 해보면 @Qualifier가 추가된 것을 볼 수 있다.

 

 

테스트도 무사히 통과 😎

 

 

 

이것으로 기존에 목표했던 다중DB / JPA / MyBatis / QueryDSL 사용 설정을 모두 완료했다! 😆