line / kotlin-jdsl

Kotlin library that makes it easy to build and execute queries without generated metamodel
https://kotlin-jdsl.gitbook.io/docs/
Apache License 2.0
651 stars 85 forks source link

Multiple Databases 설정시 추가 작업 해줘야 하는 방법이 있나요? #684

Closed ahnjunwoo-rsquare closed 2 months ago

shouwn commented 2 months ago

@ahnjunwoo-rsquare 안녕하세요.

Kotlin JDSL은 쿼리를 생성하는 라이브러리이기 때문에 Kotlin JDSL을 위한 별도의 추가작업은 필요 없습니다.

ahnjunwoo-rsquare commented 2 months ago

멀티 데이터베이스 커넥션 설정시 아래의 transactionManager설정되어진 entitymanager xx.domain jpa 엔티티 모델이 존재하는데 계속 찾지 못한다는 이슈가 발생하는데 예를들어서

@Configuration @EnableJpaRepositories( basePackages = [ "xx.jpa.repository", ], entityManagerFactoryRef = "xxDbEntityFactory", transactionManagerRef = "xxDbTransactionManager", ) class CoreDbDataSourceConfig { @Bean(name = ["xxDbDataSource"]) @ConfigurationProperties(prefix = "spring.datasource.read") fun dataSource(): DataSource = DataSourceBuilder.create().build()

@Bean(name = ["xxDbEntityFactory"])
fun entityManagerFactory(
    builder: EntityManagerFactoryBuilder,
    @Qualifier("coreDbDataSource") dataSource: DataSource
): LocalContainerEntityManagerFactoryBean =
    builder
        .dataSource(dataSource)
        .packages("xx.domain")
        .persistenceUnit("core-db")
        .build()

@Bean(name = ["xxDbTransactionManager"])
fun transactionManager(
    @Qualifier("coreDbEntityFactory")
    entityManagerFactory: EntityManagerFactory
): PlatformTransactionManager =
    JpaTransactionManager(entityManagerFactory)

}

@Repository class ParsingTradeRepositorySupport( private val parsingTradeRepository: ParsingTradeRepository, ) { fun getParsingTrades( registrationId: Long, pageable: Pageable, ): Page<ParsingTrade?> { val pageParsingTrade = parsingTradeRepository.findPage(pageable) { select( entity(ParsingTrade::class), ).from( entity(ParsingTrade::class), ).whereAnd( path(ParsingTrade::registrationId).eq(registrationId), ).orderBy( Sorts.desc(Paths.path(ParsingTrade::listNo), nullOrder = Sort.NullOrder.LAST), ) } return pageParsingTrade } }

Caused by: org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'ParsingTrade' 엔티티를 찾지 못하는 이슈가 발생합니다. (xx.domain에 엔티티는 조회, jpa 조회같은 경우는 정상적으로 동작) 6시간 삽질중인데 예를들어 querydsl처럼 엔티티 매니저를 설정하는방법에 대해서 문의드립니다.

@Configuration public class QueryDslConfig {

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

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

@Bean
public JPAQueryFactory firstJpaQueryFactory() {
    return new JPAQueryFactory(firstEntityManager);
}

@Bean
public JPAQueryFactory secondJpaQueryFactory() {
    return new JPAQueryFactory(secondEntityManager);
}

}

shouwn commented 2 months ago

@ahnjunwoo-rsquare 안녕하세요.

Spring 환경에서 Multiple Datasource를 사용하는 방법은 총 2가지가 있습니다.

EntityManager 직접 사용

JpaRepository 없이 EntityManager만으로 Kotlin JDSL을 통해 생성된 쿼리를 실행할 수 있습니다. 아래 createQuery 메소드는 Spring Support에서 제공하는 확장함수로 jakarta 버전의 JPA를 사용하신다면 com.linecorp.kotlinjdsl.support.spring.data.jpa.extension 패키지를 통해 사용하실 수 있습니다.

val query = jpql {
    select(
        path(Author::authorId),
    ).from(
        entity(Author::class),
        join(BookAuthor::class).on(path(Author::authorId).equal(path(BookAuthor::authorId))),
    ).groupBy(
        path(Author::authorId),
    ).orderBy(
        count(Author::authorId).desc(),
    )
}

val actual = entityManager.createQuery(query, context).setMaxResults(1).resultList.first()

Transactional 사용

JpaRepository가 참고하는 EntityManager는 Transaction을 통해 결정됩니다.

Spring이 주입해주는 EntityManager는 Proxy 객체로, 실제 사용될 때 다이나믹하게 주입됩니다. 주입될 EntityManager는 Transaction을 통해 결정되며, 현재 진행중인 Transaction의 TransactionManager가 가지고 있는 EntityManagerFactory로 EntityManager를 생성합니다.

이 방식을 이용해 JpaRepository 호출 전 Transactional 어노테이션에 Datasource에 따른 TransactionManager를 지정하는 것으로 쿼리가 실행될 Datasource를 선택할 수 있습니다.

@Repository
@Transactional("xxDbTransactionManager")
class ParsingTradeRepositorySupport(
    private val parsingTradeRepository: ParsingTradeRepository,
) {
    fun getParsingTrades(
        registrationId: Long,
        pageable: Pageable,
    ): Page<ParsingTrade?> {
        val pageParsingTrade = parsingTradeRepository.findPage(pageable) {
            select(
                entity(ParsingTrade::class),
            ).from(
                entity(ParsingTrade::class),
            ).whereAnd(
                path(ParsingTrade::registrationId).eq(registrationId),
            ).orderBy(
                Sorts.desc(Paths.path(ParsingTrade::listNo), nullOrder = Sort.NullOrder.LAST),
            )
        }

        return pageParsingTrade
    }
}