Spring Dynamic DataSource Routing

Spring Dynamic DataSource Routing(AbstractRoutingDataSource)

AbstractRoutingDataSource.java 클래스에 abstract 메소드로 determineCurrentLookupKey 존재한다.

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
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}

protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
@Nullable
protected abstract Object determineCurrentLookupKey();
}

대충 코드를 보면 위와같은 내용인데 getConnection 할때 determineCurrentLookupKey를 통해 resolvedDataSources 데이터에 있는 connection을 찾아서 해당 커넥션을 넘기는 방법이다.

그럼 간단한 예제를 보면 이해하기 쉬울것이다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public enum RoutingDataSourceLookupKey{
READ,WRITE
}

public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? RoutingDataSourceLookupKey.READ : RoutingDataSourceLookupKey.WRITE;
}
}

@Configuration
public class DataSourceConfigration{
@Bean
@ConfigurationProperties(prefix = "test.datasource-read")
public DataSource readDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}

@Bean
@ConfigurationProperties(prefix = "test.datasource-write")
public DataSource writeDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}

@DependsOn({"readDataSource","writeDataSource"})
@Bean
public DataSource routingDataSource(DataSource writeDataSource, DataSource readDataSource) {

var routingDataSource = new ReplicationRoutingDataSource();
var dataSourceMap = new HashMap<>();
dataSourceMap.put(RoutingDataSourceLookupKey.WRITE, writeDataSource);
dataSourceMap.put(RoutingDataSourceLookupKey.READ, readDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(readDataSource);

return routingDataSource;
}

@DependsOn("routingDataSource")
@Primary
@Bean
public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
}

@Configuration
public class JpaDataSourceConfigration {

@Bean
@ConfigurationProperties("spring.jpa")
public JpaProperties jpaProperties() {
return new JpaProperties();
}

@Bean
@ConfigurationProperties("spring.jpa.hibernate")
public HibernateProperties hibernateProperties() {
return new HibernateProperties();
}

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
JpaProperties jpaProperties, HibernateProperties hibernateProperties) {

LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPersistenceProvider(new HibernatePersistenceProvider());
entityManagerFactory.setPersistenceUnitName("entityManagerUnit");
entityManagerFactory.setPackagesToScan("io.lific.product.infrastructure.jpa");
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setGenerateDdl(jpaProperties.isGenerateDdl());
jpaVendorAdapter.setShowSql(jpaProperties.isShowSql());
if (jpaProperties.getDatabase() != null) {
jpaVendorAdapter.setDatabase(jpaProperties.getDatabase());
}
if (jpaProperties.getDatabasePlatform() != null) {
jpaVendorAdapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
}
var properties= jpaProperties.getProperties();
properties.put("hibernate.physical_naming_strategy", hibernateProperties.getNaming().getPhysicalStrategy());
properties.put("hibernate.implicit_naming_strategy", hibernateProperties.getNaming().getImplicitStrategy());
properties.put("hibernate.hbm2ddl.auto",hibernateProperties.getDdlAuto());
jpaProperties.setOpenInView(jpaProperties.getOpenInView());
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
entityManagerFactory.setJpaPropertyMap(properties);
entityManagerFactory.afterPropertiesSet();

return entityManagerFactory;
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}

@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
}


위와같은 방법으로도 처리를 할수있다.

LazyConnectionDataSourceProxy 를 꼭 해줘야 TransactionSynchronizationManager.isCurrentTransactionReadOnly() 값을 정확하게 읽어 드릴수 있다.

참고자료