관리 메뉴

IT.FARMER

Spring Muiltiple DataSource 본문

Spring

Spring Muiltiple DataSource

아이티.파머 2021. 4. 29. 20:01
반응형

Spring Muiltiple DataSource

처음에 멀티데이터 소스를 사용할땐 DB 한곳을 바라보고 여러가지 프레임웍 즉 JPA , Mybatis 를 이요하기 위해 사용 했는데. 생각해보면 하나더 있었다. DB 여러(n)대를 하나의 Module 에서 바라보게 되는 것이다. 기본적으로 Spring 은 perperties 에 하나의 data source 를 연결하기 위해 지원하기 때문에 여러개의 datasource를 사용하기 위해서는 사용자가 직접 정의하는 방법을 택해야 한다.

멀티 데이터소스를 설정하다보면 다음과 같은 오류에 만날때가 많다. 기본적으로 어떤 오류인지 알고가자

Caused by: java.lang.IllegalArgumentException: Not a managed type: class XXXEntity

BeanDefinitionOverrideException: Invalid bean definition with name 'XXXRepository' defined in null: Cannot register bean definition

말그대로 Bean을 생성할수도 없고 Entity manager로도 사용 할수 없다는 건데, 설정을 잘못하면 나타나는 증상이다. 기본적으로 @SpringBootApplication 이라는 것을 우린 SprigBoot를 사용하며 어플리케이션을 Start 할때 사용한다.

여기서 가장 기본적으로 @Compoment,Controller,Service,Repository.. 등등 여러가지들을 기본적으로 스켄 하게 된다.

그렇기 때문에 각자 Java Configuration 으로 각자의 스켄범위와 설정을 해주도록 한다.

예를 들면 다음과 같다.

mybatis 와 JPA의 멀티 커넥션 모듈을 구성하기 위해서 다음과 같이 구성한다.

1. DB 하나에 여러 Persistence Muilty DataSource

Mybatis Configuration

@Lazy
@Configuration
@EnableTransactionManagement
@MapperScan(
        basePackageClasses = {UserConnectionMapper.class}
        //, basePackages = {"com.cjenm.report.crawler.repository.mapper"}
        , sqlSessionFactoryRef = "mybatisSqlSessionFactory"

)
public class DataSourceMybatisConfig implements EnvironmentAware {

    @Resource
    public MybatisProperties mybatisProperties;

    @Bean(name = "mybatisDataSource")
    public DataSource mybatisDataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(env.getProperty("spring.datasource.hikari.driver-class-name"));
        driverManagerDataSource.setUrl(env.getProperty("spring.datasource.hikari.jdbc-url"));
        driverManagerDataSource.setUsername(env.getProperty("spring.datasource.hikari.username"));
        driverManagerDataSource.setPassword(env.getProperty("spring.datasource.hikari.password"));

        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDataSource(driverManagerDataSource);

        return hikariDataSource;
    }

    @Bean(name = "transactionManagerMybatis")
    public PlatformTransactionManager transactionManager(@Qualifier("mybatisDataSource") DataSource masterDataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(masterDataSource);
        transactionManager.setGlobalRollbackOnParticipationFailure(false);
        return transactionManager;
    }

    /**
     * YAML 에설정한 Mybatis Configuration 설정 포함
     */
    @Bean(name = "mybatisSqlSessionFactory")
    public SqlSessionFactory mybatisSqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(this.mybatisDataSource());

        // YAML에 포함
        sqlSessionFactoryBean.setConfiguration(mybatisProperties.getConfiguration());
        sqlSessionFactoryBean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mybatisProperties.getMapperLocations()[0]));//("classpath:mapper/*.xml"));

        return (SqlSessionFactory) sqlSessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(this.mybatisSqlSessionFactory());
    }

    @Autowired
    @Lazy
    private Environment env;

    /* (non-Javadoc)
     * @see org.springframework.context.EnvironmentAware#setEnvironment(org.springframework.template.template.env.Environment)
     */
    @Override
    public void setEnvironment(@Nonnull Environment environment) {
        env = environment;
    }

application.yml 의 proterties를 그대로 사용하려다 보니 코드가 길어 졌지만, factoty , transactionManager 와 SqlSessionTemplate 만 Bean으로 재지정 해주면 된다.

JPA Configration

@Lazy
@Configuration
@EntityScan(
        basePackages = "com.cjenm.report.library.domain.entity"
)
@EnableJpaRepositories(
        transactionManagerRef = "jpaTransactionManager",
//        entityManagerFactoryRef = "jpaEntityManagerFactory",
         basePackages = {"com.cjenm.report.library.repository.jpa"}
)
@EnableTransactionManagement
public class DataSourceJpaConfig {
    private final DataSource dataSource;
    private final JpaProperties properties;

    public DataSourceJpaConfig(@Qualifier("mybatisDataSource") DataSource dataSource, JpaProperties properties) {
        this.dataSource = dataSource;
        this.properties = properties;
    }

    @Bean(name = "jpaTransactionManager")
    public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }
}

앞서 생성한 mybatisDataSource 를 기반으로 생성하고 TrancationManager가 겹치지 않게 paTransactionManager도 생성해 준다. @Trancstion("jpaTransactionManager") 으로 사용한다.

2. 여러 DB에 Muilty DataSource

앞서 모듈에서는 여러 persistence layer를 사용할때 멀티 데이터 소스 생성법이 었다면, 지금설정은하나의 모듈에서 여러개의 DB로 붙고자 하는경우의 custom muiltiple datasource 이다. 하나만 알게 되면 mybatis, jpa, r2dbc 모두 가능하다.

mybatis 와 jpa 의 멀티 데이터 소스에 대해 다루어 보자.

JPA multiple

앞서 공부했듯이 DataSource는 기본적으로 필요하며, jpaDataSource가 만들어져 있다고 가정하자 이에 하나의 도메인은 server 또하나의 도메인은 client 라고 지정후 각자의 DB가 다른 가정하에 jpaDataSource를 지정하자 .

app.datasource.client.url=jdbc:h2:mem:~/test
app.datasource.client.driver-class-name=org.h2.Driver

app.datasource.server.driver-class-name=com.mysql.jdbc.Driver
app.datasource.server.url=jdbc:mysql://localhost:3306/v?autoReconnect=true&useSSL=false
app.datasource.server.username=myuser
app.datasource.server.password=mypass

1. 서로다른 저장소 package 생성

먼저 서로 다른 pacakge 에 Repository를 설정한다.

Server 도메인 package →spring.server.repository

import org.springframework.data.jpa.repository.JpaRepository;

public interface ServerRepository extends JpaRepository<Server,Long> {
}

Client 도메인 package →spring.client.repository

import org.springframework.data.jpa.repository.JpaRepository;

public interface ClientRepository extends JpaRepository<Client,Long> {
}

2. 두개의 JPA DataSource CustomConfiguration 생성

위에 정의된 서로다른 도메인과 repository 의 패키지의 Configuration을 정의한다.

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "clientEntityManager",
        transactionManagerRef = "clientTransactionManager",
        basePackages = {"org.spring.repository"}
        )
public class ClientConfig {

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "serverEntityManager",
        transactionManagerRef = "serverTransactionManager",
        basePackages = {"spring.server.repository"}
        )
public class ServersConfig {

3. 하나의 데이터 소스를 기본으로 설정한다 @Primary

Parameter 0 of constructor in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration required a single bean, but 2 were found 하나의 기본 데이터 소스를 지정하지 않으면 오류 발생

@Bean("serverDataSourceProperties")
@Primary
@ConfigurationProperties("app.datasource.servers")
public DataSourceProperties serversDataSourceProperties(){
    return new DataSourceProperties();
}

@Bean("serverDataSource")
@Primary
@ConfigurationProperties("app.datasource.servers")
public DataSource serversDataSource(@Qualifier("serverDataSourceProperties") DataSourceProperties serverDataSourceProperties) {
    return serverDataSourceProperties().initializeDataSourceBuilder().build();
}

Server - JPA 설정 전체

Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "serverEntityManager",
        transactionManagerRef = "serverTransactionManager",
        basePackages = {"spring.server.repository"}
        )
public class ServersConfig {

    @Bean(name = "serverEntityManager")
    public LocalContainerEntityManagerFactoryBean getServersEntityManager(EntityManagerFactoryBuilder builder,
                                                                          @Qualifier("serverDataSource") DataSource serverDataSource){

        return builder
                .dataSource(serverDataSource)
                .packages("spring.domain.server")
                .persistenceUnit("servers")
                .properties(additionalJpaProperties())
                .build();

    }

    Map<String,?> additionalJpaProperties(){
        Map<String,String> map = new HashMap<>();

        map.put("hibernate.hbm2ddl.auto", "create");
        map.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        map.put("hibernate.show_sql", "true");

        return map;
    }

    @Bean("serverDataSourceProperties")
    @Primary
    @ConfigurationProperties("app.datasource.servers")
    public DataSourceProperties serverDataSourceProperties(){
        return new DataSourceProperties();
    }

    @Bean("serverDataSource")
    @Primary
    @ConfigurationProperties("app.datasource.servers")
    public DataSource serversDataSource(@Qualifier("serverDataSourceProperties") DataSourceProperties serverDataSourceProperties) {
        return serversDataSourceProperties().initializeDataSourceBuilder().build();
    }

    @Bean(name = "serverTransactionManager")
    public JpaTransactionManager transactionManager(@Qualifier("serverEntityManager") EntityManagerFactory serverEntityManager){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(serversEntityManager);

        return transactionManager;
    }
}

Client - JPA 설정 전체

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "clientEntityManager",
        transactionManagerRef = "clientTransactionManager",
        basePackages = {"org.spring.repository"}
        )
public class DomainsConfig {

    @Bean(name = "clientEntityManager")
    public LocalContainerEntityManagerFactoryBean getdomainsEntityManager(EntityManagerFactoryBuilder builder
    ,@Qualifier("clientDataSource") DataSource clientDataSource){

        return builder
                .dataSource(domainsDataSource)
                .packages("spring.domain.client")
                .persistenceUnit("client")
                .properties(additionalJpaProperties())
                .build();

    }

    Map<String,?> additionalJpaProperties(){
        Map<String,String> map = new HashMap<>();

        map.put("hibernate.hbm2ddl.auto", "create");
        map.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        map.put("hibernate.show_sql", "true");

        return map;
    }

    @Bean("clientDataSourceProperties")
    @ConfigurationProperties("app.datasource.client")
    public DataSourceProperties domainsDataSourceProperties(){
        return new DataSourceProperties();
    }

    @Bean("clientsDataSource")
    public DataSource domainsDataSource(@Qualifier("clientDataSourceProperties") DataSourceProperties clientDataSourceProperties) {
        return domainsDataSourceProperties.initializeDataSourceBuilder().build();
    }

    @Bean(name = "clientTransactionManager")
    public JpaTransactionManager transactionManager(@Qualifier("clientEntityManager") EntityManagerFactory clientEntityManager){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(domainsEntityManager);

        return transactionManager;
    }

}

이렇게 이기종간 Database (client - H2, server - Mysql) 의 멀티플데이터 소스를 구현 할 수 있다.

Spring Data JPA - Multiple EnableJpaRepositories

[Spring] Spring Boot 2 Multiple DataSource

반응형