이글은 Spring Boot AutoConfigure에 관해 글을 작성하던 도중에 예제를 작성하다 이제는 정말 미루고 미루고 미루다. 이제는 정말 작성해야 된다고 생각하여 작성한다.
본 내용은 Spring Data JPA
를 활용하여 2개 이상의 데이터베이스를 사용해야 하는 경우 설정을 Spring Boot 공식문서를 기반으로 코드를 작성하였다.
먼저 application.yml
에 정의는 다음과 같이 하였다. spring.datasource1
, spring.datasource2
와 같은 접두사는 상황에 따라 변경하여도 문제는 없다.
여기서 공식문서에 없고 내가 추가한 부분은 datasource.jpa.hibernate
이다.
각 데이터베이스의 특성에 따라 명명규칙을 다르게 설정해야 하는 경우가 있을 수 있기 때문에 해당 속성을 추가하였다. 이에 따라 공식 문서에는 없는 설정들도 추가가 된다.
spring:
application:
name: sample
datasource1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test
username: root
password: root
hikari:
maximum-pool-size: 5
jpa:
hibernate:
ddl-auto: create-drop
naming:
# implicit-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
datasource2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/board
username: root
password: root
hikari:
maximum-pool-size: 3
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
각각의 DataSource
를 생성한다.
@Bean
@ConfigurationProperties(prefix = "spring.datasource1")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource1.hikari")
public DataSource primaryDataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
각각의 HibernateProperties
, JpaProperties
를 통해서 EntityManagerFactory
를 생성한다.
createJpaVendorAdapter
는 Spring Boot AutoConfigure 로직를 그대로 활용하였다.
createEntityManagerFactoryBuilder
는 Srping Boot 공식 문서를 활용하여 datasource.jpa.hibernate
를 추가하기 위해서 Spring Boot AutoConfigure 로직을 참고하여 약간 변경하였다.
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource1.jpa.hibernate")
public HibernateProperties primaryHibernateProperties() {
return new HibernateProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource1.jpa")
@Primary
public JpaProperties primaryJpaProperties() {
return new JpaProperties();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(@Qualifier("primaryDataSource") DataSource dataSource,
@Qualifier("primaryJpaProperties") JpaProperties jpaProperties,
@Qualifier("primaryHibernateProperties") HibernateProperties hibernateProperties) {
EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties, hibernateProperties);
return builder.dataSource(dataSource).packages("io.velog.youmakemesmile.primary").build();
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
Map<String, Object> jpaProperties1 = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings().ddlAuto(hibernateProperties::getDdlAuto).hibernatePropertiesCustomizers(new ArrayList<>()));
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties1, null);
}
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties properties) {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(properties.isShowSql());
if (properties.getDatabase() != null) {
adapter.setDatabase(properties.getDatabase());
}
if (properties.getDatabasePlatform() != null) {
adapter.setDatabasePlatform(properties.getDatabasePlatform());
}
adapter.setGenerateDdl(properties.isGenerateDdl());
return adapter;
}
각각의 TransactionManager
생성이 필요하다. 그리고 로직이 두개의 데이터베이스가 분리되어 동작한다고 한다면
@Transactional(transactionManager = "primaryTransactionManager")
이와 같이 로직 마다 각각의 TrasactionManager
를 지정해야한다. 만약 두개의 데이터베이스가 모두 사용되는 로직인 경우 JTA
를 적용해야하지만 현재는 Deprecated
상태인 ChainedTransactionManager
를 사용하면 반절의 회피?는 가능하다. ChainedTransactionManager
도 회피일 뿐이지 해결 방법은 아니다!!!
@Bean
public JpaTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory) {
return new JpaTransactionManager(primaryEntityManagerFactory);
}
@Bean
@Primary
public ChainedTransactionManager transactionManager(@Qualifier("primaryTransactionManager") JpaTransactionManager primaryTransactionManager, @Qualifier("secondTransactionManager") JpaTransactionManager secondTransactionManager) {
return new ChainedTransactionManager(secondTransactionManager, primaryTransactionManager);
}
최종 설정 코드는 다음과 같다.
@Configuration
@EnableJpaRepositories(
basePackages = {"io.velog.youmakemesmile.primary"},
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
@EnableTransactionManagement
public class PrimaryConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource1")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource1.hikari")
public DataSource primaryDataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource1.jpa.hibernate")
public HibernateProperties primaryHibernateProperties() {
return new HibernateProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource1.jpa")
@Primary
public JpaProperties primaryJpaProperties() {
return new JpaProperties();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(@Qualifier("primaryDataSource") DataSource dataSource,
@Qualifier("primaryJpaProperties") JpaProperties jpaProperties,
@Qualifier("primaryHibernateProperties") HibernateProperties hibernateProperties) {
EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties, hibernateProperties);
return builder.dataSource(dataSource).packages("io.velog.youmakemesmile.primary").build();
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
Map<String, Object> jpaProperties1 = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings().ddlAuto(hibernateProperties::getDdlAuto).hibernatePropertiesCustomizers(new ArrayList<>()));
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties1, null);
}
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties properties) {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(properties.isShowSql());
if (properties.getDatabase() != null) {
adapter.setDatabase(properties.getDatabase());
}
if (properties.getDatabasePlatform() != null) {
adapter.setDatabasePlatform(properties.getDatabasePlatform());
}
adapter.setGenerateDdl(properties.isGenerateDdl());
return adapter;
}
@Bean
// @Primary
public JpaTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory) {
return new JpaTransactionManager(primaryEntityManagerFactory);
}
@Bean
@Primary
public ChainedTransactionManager transactionManager(@Qualifier("primaryTransactionManager") JpaTransactionManager primaryTransactionManager, @Qualifier("secondTransactionManager") JpaTransactionManager secondTransactionManager) {
return new ChainedTransactionManager(secondTransactionManager, primaryTransactionManager);
}
}
@AutoConfiguration
@EnableJpaRepositories(
basePackages = {"io.velog.youmakemesmile.second"},
entityManagerFactoryRef = "secondEntityManagerFactory",
transactionManagerRef = "secondTransactionManager"
)
public class SecondConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource2")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource2.hikari")
public DataSource secondDataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource2.jpa.hibernate")
public HibernateProperties secondHibernateProperties() {
return new HibernateProperties();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource2.jpa")
@Primary
public JpaProperties secondJpaProperties() {
return new JpaProperties();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(@Qualifier("secondDataSource") DataSource dataSource,
@Qualifier("secondJpaProperties") JpaProperties jpaProperties,
@Qualifier("secondHibernateProperties") HibernateProperties hibernateProperties) {
EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties, hibernateProperties);
return builder.dataSource(dataSource).persistenceUnit("second").packages("io.velog.youmakemesmile.second").build();
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
Map<String, Object> jpaProperties1 = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings().ddlAuto(hibernateProperties::getDdlAuto).hibernatePropertiesCustomizers(new ArrayList<>()));
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties1, null);
}
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties properties) {
// ... map JPA properties as needed
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(properties.isShowSql());
if (properties.getDatabase() != null) {
adapter.setDatabase(properties.getDatabase());
}
if (properties.getDatabasePlatform() != null) {
adapter.setDatabasePlatform(properties.getDatabasePlatform());
}
adapter.setGenerateDdl(properties.isGenerateDdl());
return adapter;
}
@Bean
public JpaTransactionManager secondTransactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory secondEntityManagerFactory) {
return new JpaTransactionManager(secondEntityManagerFactory);
}
}
해당 설정은 JPA
를 활용하여 2개의 DataSource
를 연결한 경우이고 이외에 MyBatis
, Jdbc
등 여러 조합을 통해서 두개 이상의 데이터베이스를 설정하는 경우도 추후... 미루고 미루고 미루다 작성해보겠다.