티스토리 뷰

반응형

 

 

 

Spring



 

 

 

지난번에 이기종 DB 를 연결하기 위해 

Datasource 를 분리하는 작업을 했었는데

거기에 덧붙여 JPA와 Mybatis를 사용할 수 있게하고 QueryDSL 까지 사용하려고 한다.

 

 

 

[SpringBoot JPA] 다중 DB 설정하기 (multi Datasource + 이기종 DB)

간혹 사이트들을 연계해야 할때, 그 중에서도 api 없는 사이트의 데이터를 사용해야할 때, 하나의 웹에서 여러 DB를 연결 시켜 사용해야 할 때가 있다. 간단하게 DB Link 로 해결하려 했으나 메인으

jong-bae.tistory.com

 

 

JPA 설정만으로 분리하는 것은 위 전 포스팅과 같이 매우 간편하게 설정할 수 있었다.

하지만 필자 프로젝트에 Mybatis도 간혹 필요한 부분이 있어서 설정하는데

약간의 예상치 못한 시행착오들이 있어 포스팅 해본다.

 

 

 

 

프로퍼티 구조는 아래와 같다.

url을 jdbc-url 로 변경하는 것 잊지마시길!

 

spring:
  primary-datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    jdbc-url : ENC()
    username: ENC()
    password: ENC()

  second-datasource:
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    jdbc-url: ENC()
    username: ENC()
    password: ENC()

 

 

 

그리고 주DB와 서브DB의 설정클래스를 아래와 같이 작성했다.

 

@Configuration
@EnableJpaRepositories(
    basePackages = {
    	"com.sample.core.common"
            , "com.sample.core.manage"
            , "com.sample.core.api"
            },
    entityManagerFactoryRef = "primaryEntityManager",
    transactionManagerRef = "primaryTransactionManager"
)
@MapperScan(
    sqlSessionFactoryRef = "primarySessionFactory",
    value = {
    	"com.sample.core.common.mybatis"
            , "com.sample.core.manage.**.mapper"
    }
)
public class PrimaryConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.primary-datasource")
    public DataSource primaryDatasource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManager() {
        LocalContainerEntityManagerFactoryBean managerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        managerFactoryBean.setDataSource(primaryDatasource());
        managerFactoryBean.setPersistenceUnitName("primaryPersistence");
        managerFactoryBean.setPackagesToScan(new String[] {
                "com.sample.core.common"
                , "com.sample.core.manage"
                , "com.sample.core.api"
        });

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        managerFactoryBean.setJpaVendorAdapter(vendorAdapter);

        HashMap<String, Object> prop = new HashMap<>();
        prop.put("hibernate.hbm2ddl.auto", "validate");
        managerFactoryBean.setJpaPropertyMap(prop);

        return managerFactoryBean;
    }

    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager() {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(primaryEntityManager().getObject());
        return jpaTransactionManager;
    }

    @Bean(name = "primarySessionFactory")
    @Primary
    public SqlSessionFactory primarySessionFactory(ApplicationContext applicationContext) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(primaryDatasource());
        sqlSessionFactoryBean.setTypeAliasesPackage("com.sample.core");
        sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath*:/com/sample/core/**/*Mapper.xml"));
        sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
        return sqlSessionFactoryBean.getObject();
    }
}

 

@Configuration
@EnableJpaRepositories(
    basePackages = {"com.sample.core.homepage"},
    entityManagerFactoryRef = "secondEntityManager",
    transactionManagerRef = "secondTransactionManager"
)
@MapperScan(
        sqlSessionFactoryRef = "secondSessionFactory",
        value = {"com.sample.core.homepage.**.mapper" }
)
public class SecondConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.second-datasource")
    public DataSource secondDatasource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean secondEntityManager() {
        LocalContainerEntityManagerFactoryBean managerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        managerFactoryBean.setDataSource(secondDatasource());
        managerFactoryBean.setPersistenceUnitName("secondPersistence");
        managerFactoryBean.setPackagesToScan(new String[] {
                "com.sample.core.homepage"
        });

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);
        managerFactoryBean.setJpaVendorAdapter(vendorAdapter);

        HashMap<String, Object> prop = new HashMap<>();
        prop.put("hibernate.hbm2ddl.auto", "update");
        managerFactoryBean.setJpaPropertyMap(prop);

        return managerFactoryBean;
    }

    @Bean
    public PlatformTransactionManager secondTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(secondEntityManager().getObject());
        return transactionManager;
    }

    @Bean(name = "secondSessionFactory")
    public SqlSessionFactory secondSessionFactory(ApplicationContext applicationContext) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(secondDatasource());
        sqlSessionFactoryBean.setTypeAliasesPackage("com.sample.core.homepage");
        sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath*:/com/sample/core/homepage/**/*Mapper.xml"));
        sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
        return sqlSessionFactoryBean.getObject();
    }
}

 

 

기존 다중 Datasource 설정에 mybatis를 위한 mapperscan과 SqlSessionFactory 를 작성해줬다.

그리고 application.yml 에 입력한 mybatis 설정이 설정대로 동작하지 않는 것 같아 mybatis-config.xml 로 작성해서 리소스를 참조하게 변경하였다.

 

다중 Datasource 를 이용할때는 레파지토리가 있는 패키지 위치를 확실히 구분지어 사용해야한다.

예전 mybatis만 쓸때는 SqlSession bean 이름을 각각 설정해 한 레파지토리에서 사용 가능했지만

JPA 는 구분지어줘야 올바르게 패키지 내의 엔티티를 스캔하여 접근을 한다.

 

 

 

프로젝트 진행 중에 DB를 나누게 되면서 전체구조를 틀기엔 부담이 있어

특정 패키지만 서브DB 를 사용하기로 하고

Primary 쪽에는 패키지명을 다수 나열하게 됐다.

 

 

여기까지 패키지가 잘 나눠져 계시고 특이사항이 없다면 잘 구동 되실 겁니다.

 

 

 

 

 

 

 

 

 


 

 

그리고 작업중에 전혀 예상치 못했던 부분을 마주하게 됐는데...

Mybatis가 Mapper 라고 명칭된 인터페이스들을 모두 끌어가는 것이었다.

 

해당 프로젝트에는 Mybatis @Mapper와  Mapstruct @Mapper 인터페이스가 각각 2개씩 존재하는데

Mybatis가 Mapstruct 매퍼까지 인식해서 오류가 발생했다.

 

import org.mapstruct.Mapper;

import org.apache.ibatis.annotations.Mapper;

 

아니 두분 전혀 다르신 분인데요...ㅋ

 

 

파일이 아래와 같이 구분되어져 있는데...

mapstruct to Mapper

 

mybatis mapper

 

 

 

mybatis 가 *ToMapper 로 되어 있는 인터페이스들을 인식하면서 BeanCreationException 이 발생하는데...

 

 Initialization of bean failed; 
 nested exception is org.springframework.beans.ConversionNotSupportedException: 
 Failed to convert property value of type 'org.mybatis.spring.SqlSessionTemplate' to required type 'org.apache.ibatis.session.SqlSessionFactory' for property 'sqlSessionFactory'; 
 nested exception is java.lang.IllegalStateException: 
 Cannot convert value of type 'org.mybatis.spring.SqlSessionTemplate' to required type 'org.apache.ibatis.session.SqlSessionFactory' for property 'sqlSessionFactory': 
 no matching editors or conversion strategy found

 

 

솔직히 왜 @mapper 어노테이션을 무시하고 저러는지는 깊게 확인 안해봤지만...

 

시간 관계상 아래 처럼 mybatis mapper 폴더를 따로 분리하여 처리하였다.

 

 

@MapperScan(
    sqlSessionFactoryRef = "primarySessionFactory",
    value = {
    	"com.sample.core.common.mybatis"
            , "com.sample.core.manage.**.mapper"
    }
)

@MapperScan(
        sqlSessionFactoryRef = "secondSessionFactory",
        value = {"com.sample.core.homepage.**.mapper" }
)

 

 

mybatis는 mapper 폴더만 보도록 고정.

 


 

 

 

 

 

그리고 QueryDsl 도 주DB를 바라보도록 아래와 같이 변경하였다.

 

EntityManagerFactoryBean에 설정한 persistenceUnitName 을 QueryDsl 설정에 명시해준다.

managerFactoryBean.setPersistenceUnitName("primaryPersistence");

managerFactoryBean.setPersistenceUnitName("secondPersistence");

 

@Configuration
public class QuerydslConfig {

    @PersistenceContext(unitName = "primaryPersistence")
    private EntityManager entityManager;
    
    @PersistenceContext(unitName = "secondPersistence")
    private EntityManager secondEntityManager;
	
    @Primary
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
    	return new JPAQueryFactory(entityManager);
    }
	
    @Bean
    public JPAQueryFactory secondJpaQueryFactory() {
    	return new JPAQueryFactory(secondEntityManager);
    }
}

 

 

기존의 Querydsl 설정에 위와 같이 추가해준 후

레파지토리에서 빈이름을 명시해줘 사용하면 된다.

 

@Repository
@RequiredArgsConstructor
public class queryRepository {

    // primary 일경우
    private final JPAQueryFactory queryFactory;

    // other 일경우
    @Autowired
    @Qualifier("secondJpaQueryFactory")
    private final JPAQueryFactory queryFactory;
 
 	...
}

 

 

여기까지하면  주DB의 JPA+QueryDsl+Mybatis가 잘 동작하고

서브DB의 JPA+Mybatis 가 잘되는 것을 확인 할 수 있었다.

 

 

 

예상치못한 mybatis 녀석때문에 쫌 많은 시간을 사용한 것 같다.

 

 

 

 

 



 

 

반응형
댓글
반응형
최근에 올라온 글
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Total
Today
Yesterday