Spring Multiple Datasource Sample 예제

업데이트:

Spring Multiple Datasource Sample 예제

설정 관련 설명

간단히 설명하지만, 매우매우 중요하니 잘 기억하기 바란다.
처음 Datasoure 에는 Bean 생성 객체들위에 @Primary 가 있어야만 한다. 나머지 것들에는 반드시 없어야 한다.
복붙하고나서 돌리면 에러가 Bean 생성 에러가 나와서 @Primary 가 없어야 동작한다는 것을 파악하기 어렵다.

패키지 구조도 매우 중요하다.
Ex) Entity 와 Repository 가 com.test.domain com.test.repo 라고 한다면 
지금 만드는 Config Java 파일의 package 구조는 com.test.config 이어야만 한다.
com.level1.level2.config 에 Config 파일 만들고
com.level1.level2.level3.domain
com.level1.level2.level3.repo 를 만들어 놓고 서버 돌리면
hikariCP, hibernate 설정이 먹지 않고 ConfigurationProperties 의 prefix 의 정보를 읽어서 Datasource 를 만든다.
정상적으로 서버가 떠서 잘 되는 구나 생각하겠지만, hikariCP 의 로깅을 DEBUG 로 해서 Connection Pool 설정
정보 로그를 보면 내가 설정한 정보는 먹지 않고 전부 default 로 세팅 되어 있음을 알 수 있다.

Primary Datasource Java Sample

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
@PropertySource({ "classpath:application.yml" })
@EnableTransactionManagement
@EnableJpaRepositories(
 entityManagerFactoryRef = "firstEntityManagerFactory",
 transactionManagerRef = "firstTransactionManager",
 basePackages = {
  "com.mig.base.first.repo"
 }
)
public class FirstDatasourceConfig {
	 @Primary
	 @Bean(name = "firstDataSource")
	 @ConfigurationProperties(prefix = "spring.datasource")
	 public DataSource firstDataSource() {
		 HikariDataSource hikariDataSource = new HikariDataSource();

         return hikariDataSource;
//	  return DataSourceBuilder.create().build();
	 }

	 @Primary
	 @Bean(name = "firstEntityManagerFactory")
	 public LocalContainerEntityManagerFactoryBean
	 firstEntityManagerFactory(
	  EntityManagerFactoryBuilder builder,
	  @Qualifier("firstDataSource") DataSource dataSource
	 ) {
	  return builder
	   .dataSource(dataSource)
	   .packages("com.mig.base.first.pojo")
	   .persistenceUnit("first")
	   .build();
	 }

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

Secondary Datasource or more Java Sample

Secondary 또는 그 이상의 Datasource 에는 @Primary 가 없다. 붙이면 Bean 생성 관련 에러가 발생했던것으로 기억한다. 참고로 hibernate 와 hikariCP 의 설정을 application.yml 에 기록해도 동작하지 않아서 매우 오랜 검색 끝에 아래와 같이 설정하여 동작하는 소스를 만들었다. 찾기 어려움으로. properties 파일이나, yml 파일에 하나의 Datasource 가 있는게 아니라면 아래의 방식을 따르기로 나는 일단 마음 먹었다. 잊어버릴가봐 기록해둠.

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.google.common.collect.ImmutableMap;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@ConfigurationProperties(prefix = "wony.datasource")
@PropertySource({ "classpath:application.yml" })
@EnableTransactionManagement
@EnableJpaRepositories(
 entityManagerFactoryRef = "wonyEntityManagerFactory",
 transactionManagerRef = "wonyTransactionManager",
 basePackages = { "com.mig.base.wony.repo" }
)
public class WonyDatasourceConfig extends HikariConfig {
	
	 @Bean(name = "wonyDataSource")
	 public DataSource wonyDataSource() {
		 
	    Properties dsProps = new Properties();
	    dsProps.put("driverClassName", "oracle.jdbc.OracleDriver");
	    dsProps.put("username", "WONY");
	    dsProps.put("password", "test");
	    dsProps.put("jdbcUrl", "jdbc:oracle:thin:@172.31.249.112:1023:TDWMES");
	    dsProps.put("connectionTimeout", 10000);// timeout 
        // 최대 Connection Pool 개수. 지금 남아있는 이슈가 하나 있는데, connection 을 close 하고 다시 pool 에 정상적으로
        //되돌려 주는 방법을 못찾겠다.
	    dsProps.put("maximumPoolSize", 30); 
        
//	    dsProps.put("idleTimeout", env.getProperty("hikari.idleTimeout", Integer.class));
//	    dsProps.put("maxLifetime", env.getProperty("hikari.maxLifetime", Integer.class));
//	    dsProps.put("leakDetectionThreshold", env.getProperty("hikari.leakDetectionThreshold", Integer.class));
//	    dsProps.put("jdbc4ConnectionTest", env.getProperty("hikari.jdbc4ConnectionTest", Boolean.class));

	    HikariConfig config = new HikariConfig(dsProps);
	    return new LazyConnectionDataSourceProxy(new HikariDataSource(config));
//		 return new LazyConnectionDataSourceProxy(new HikariDataSource(this));
//		 return DataSourceBuilder.create().build();
	 }

	 @Bean(name = "wonyEntityManagerFactory")
	 public EntityManagerFactory 
	 wonyEntityManagerFactory() {
	        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

	        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
	        factory.setDataSource(this.wonyDataSource());
	        factory.setJpaVendorAdapter(vendorAdapter);
	        factory.setJpaPropertyMap(ImmutableMap.of(
	                "hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect",
	                "hibernate.show_sql", "false",
	                "hibernate.jdbc.batch_size", "10", // 성능관련 옵션 batch_size 에 마춰서 동작하도록 개수 설정
	                "hibernate.jdbc.batch_versioned_data", "true",
	                "hibernate.order_inserts", "true"
	        ));
            // ImmutableMap 에는 최대 5개만 들어가서 2번 찍었음.
	        factory.setJpaPropertyMap(ImmutableMap.of(
                    "hibernate.order_updates", "true",
	                "hibernate.enable_lazy_load_no_trans", "true",
	                "hibernate.generate_statistics", "true" // 이걸 true 로 해놔야 insert 다건이 batch 형태로 돌아갔다는 통계 정보를 확인 할 수 있다. 중요함.
	        ));	        

	        factory.setPackagesToScan("com.mig.base.dpbas.pojo");
	        factory.setPersistenceUnitName("wony");
	        factory.afterPropertiesSet();

	        return factory.getObject();
	 }

    @Bean
    public PlatformTransactionManager wonyTransactionManager() {
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(wonyEntityManagerFactory());
        return tm;
    }	 
	 
}

설정들이 잘 되어 있는지 확인 하기 위한 logging setting

server:
  port: 8080

spring:
  application:
    name: test
    
  jpa:
    database: default
    properties:
      hibernate:
        show_sql: false
        meta_sql: false
    hibernate:
      ddl-auto: none
    show-sql: false      
    
  datasource:
    url: jdbc:oracle:thin:@IP_ADDR:PORT:XXX
    username: XXX
    password: XXX
    driver-class-name: oracle.jdbc.OracleDriver
    
wony:
  datasource:
    jdbc-url: jdbc:oracle:thin:@IP_ADDR:PORT:XXX
    username: XXX
    password: XXX
    driver-class-name: oracle.jdbc.OracleDriver    

logging:
  file: logs/application-debug.log
  pattern:
    console: "%d %-5level %logger : %msg%n"
    file: "%d %-5level [%thread] %logger : %msg%n"
  level:
    org.springframework.web: ERROR
    com.posco.mes3: DEBUG
    org.hibernate.engine: ERROR
    org.hibernate.validator: ERROR
    org.hibernate.persister: ERROR
    org.hibernate.boot: ERROR
    org.hibernate.cfg: ERROR
    org.hibernate.SQL: ERROR
    org.hibernate.loader: ERROR
    org.hibernate.event: ERROR
    org.hibernate: DEBUG
    com.zaxxer.hikari: DEBUG