본문 바로가기

Lang

[Java]여러 DB 환경에서 native query 쓸 때 orm.xml 문제

두 개 이상의 DB 환경에서 Spring Boot, JPA를 쓰려면 DB 별로 패키지를 분리해서 쓰는 방식이 일반적인 것 같아서 문서 읽으며 따라 하기 식으로 설정 끝냈습니다.

기본(적인) 설정으로는 querydsl 사용에 문제 있었지만 구조 바꾸면서 필요 없어진 부분들이라 querydsl 들어냈고 그러고는 잘 작동합니다.

그런데 개발 하는 과정에서 문제가 발생했습니다.

배치 처리할 부분을 native query 로 작성한 후 애리케이션을 실행하려고 하니 다음과 같은 에러 나면서 기동이 안되네요.

ERROR o.h.internal.SessionFactoryImpl(SessionFactoryImpl.java:333) HHH000177: Error in named query: ... org.hibernate.MappingException: Unknown entity ...
...
ERROR o.s.boot.SpringApplication(SpringApplication.java:837) Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tsEntityManagerFactory' defined in class path resource [.../TsConfig.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: ts] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.HibernateException: Errors in named queries: ...

참고한 문서들에서 안내한대로 LocalContainerEntityManagerFactoryBean 설정하는 곳에서 엔티티 패키지 스캔도 정확히 해줬는데 왜 그럴까?

문제 상황을 조금 더 자세히 정리해보겠습니다.

이기종 DB 2개를 사용하게 되었고 그에 맞추어 Data Source 명을 ts, appdb 로 하여 각기 따로 설정했습니다. ts 설정 관련 소스는 대략 이런 식입니다.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "repository 패키지",
        entityManagerFactoryRef = "tsEntityManagerFactory",
        transactionManagerRef = "tsTransactionManager"
)
public class TsConfig {
    @Primary
    @Bean(name = "tsDataSource")
    @ConfigurationProperties(prefix = "spring.datasource-ts")
    public DataSource tsDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "tsEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean tsEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("tsDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("ts용 엔티티 패키지")
                .persistenceUnit("ts")
                .build();
    }

    @Primary
    @Bean(name = "tsTransactionManager")
    public PlatformTransactionManager tsTransactionManager(
            @Qualifier("tsEntityManagerFactory") EntityManagerFactory tsEntityManagerFactory) {
        return new JpaTransactionManager(tsEntityManagerFactory);
    }
}

appdb 도 repository 패키지, entity 패키지 등 일부만 다르고 구조는 위와 같이 따로 설정했죠.

문제는 orm.xml에서 result-class로entity를 사용하는 곳에서 발생합니다.

가령, ts 용 entity 패키지를 ds.entity.ts, appdb용 entity 패키지를 ds.entity.appdb로 분리해놨고 orm.xml 에 <named-native-query name="..."result-class="ds.entity.appdb.items">이라고 구현해놓은 부분이 있다고 할 때 tsEntityManagerFactory 가 'ds.entity.appdb.items'라는 엔티티를 못찾아서 MappingException를 뱉어냅니다.

원인은 분명해 보입니다. ds.entity.appdb 패키지 내의 엔티티는 tsEntityManagerFactory 가 다룰 녀석들이 아니죠.

이 부분까지 추정했음에도 에러 수정할 때는 엉뚱한 곳으로 방향을 틀어버렸습니다 ㅜㅠ

우선 눈이 간 곳은 에러 중 'SessionFactoryImpl' 부분.

entityManagerFactory 빈에서 해줬던 것과 유사하게 SessionFactory 빈에도 해당 DB 에서만 사용할 entity 패키지만 스캔하도록 설정해주면 되려나?

@Bean
public LocalSessionFactoryBean sessionFactory() {
    final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
    sessionFactory.setDataSource(appdbDataSource());
    sessionFactory.setPackagesToScan(new String[] { "..." });

// sessionFactory.setHibernateProperties(hibernateProperties());

    return sessionFactory;
}

추가봤지만 실패.

다시 생각을 정리해봤습니다. 

tsEntityManagerFactory 가 자신의 도메인 아닌 ds.entity.appdb 패키지(를 사용하는 native query)를 어찌해보려다가 나는 에러라면 EntityManagerFactory 별로 자기 도메인 모델 사용하는 native query만 접근하도록 하면 되는 것아닐까?

그렇다면 orm.xml 파일을 각 DB 별로 분리하면 되지 않을까 싶어 설정하는 법 검색해보니 setMappingResources 를 이용해서 재설정 가능하군요.

@Bean(name = "tsEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean tsEntityManagerFactory(
        EntityManagerFactoryBuilder builder,
        @Qualifier("tsDataSource") DataSource dataSource) {
    return builder
            .dataSource(dataSource)
            .packages("com.clt.eagleeye.datacenter.entity.ts")
            .persistenceUnit("ts")
            .mappingResources("META-INF/ts.xml")
            .build();
}

orm.xml 과 문제 생긴 DB 용으로 ts.xml 이란 파일 분리한 후 해서 테스트해봤는데 동일한 에러 납니다. 하지만 이 문제는 쉽게 추정 가능했습니다. 

Spring Boot 가 orm.xml 은 기본 로딩하니 tsEntityManagerFactory 가 여전히 엉뚱한 도메인 모델에 접근하게 된걸거고 그래서 orm.xml 을 마저 이름 바꾸어 설정 추가해주고 테스트해보니 에러 없이 정상 동작합니다.

이렇게 차분히 글로 정리하다보니 초반에 찾은 원인에서 해법에 거의 직접 다다를 수 있었겠구나 하는 뒤늦은 후회합니다. 글쓰기 하듯 문제 정의에 조금만 더 시간 투자했다면 불필요한 에너지 많이 줄일 수 있었을 듯.