Skip to content

Commit 6204889

Browse files
author
何惠民
committed
Completed multiple and dynamic datasource and load balance
1 parent f20c348 commit 6204889

15 files changed

+238
-96
lines changed

src/main/java/cn/com/hellowood/dynamicdatasource/DynamicDataSourceApplication.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5-
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
65

7-
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
6+
@SpringBootApplication
87
public class DynamicDataSourceApplication {
98

109
public static void main(String[] args) {

src/main/java/cn/com/hellowood/dynamicdatasource/common/CommonConstant.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@
99
*/
1010
public class CommonConstant {
1111

12-
//Request result message
12+
/**
13+
* Request result message
14+
*/
1315
public static final String DEFAULT_SUCCESS_MESSAGE = "success";
1416
public static final String DEFAULT_FAIL_MESSAGE = "fail";
1517
public static final String NO_RESULT_MESSAGE = "no result";
1618

17-
//Operation status
19+
/**
20+
* Operation status
21+
*/
1822
public static final String SUCCESS = "SUCCESS";
1923
public static final String ERROR = "ERROR";
2024

21-
//Error or exception message
25+
/**
26+
* Error or exception message
27+
*/
2228
public static final String DB_ERROR_MESSAGE = "Database Error";
2329
public static final String SERVER_ERROR_MESSAGE = "Server Error";
2430
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package cn.com.hellowood.dynamicdatasource.common;
2+
3+
/**
4+
* The enum Data source key.
5+
*
6+
* @author HelloWood
7+
* @date 2017-08-15 14:26
8+
* @Email hellowoodes@gmail.com
9+
*/
10+
public enum DataSourceKey {
11+
/**
12+
* Master data source key.
13+
*/
14+
master,
15+
/**
16+
* Slave alpha data source key.
17+
*/
18+
slaveAlpha,
19+
/**
20+
* Slave beta data source key.
21+
*/
22+
slaveBeta,
23+
/**
24+
* Slave gamma data source key.
25+
*/
26+
slaveGamma
27+
}

src/main/java/cn/com/hellowood/dynamicdatasource/common/ResponseUtil.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ public static CommonResponse generateResponse(Object data) {
7575
} else {
7676
commonResponse
7777
.setCode(ResponseCode.SUCCESS)
78-
.setMessage(CommonConstant.NO_RESULT_MESSAGE)
79-
.setData(data);
78+
.setMessage(CommonConstant.NO_RESULT_MESSAGE);
8079

8180
}
8281
return commonResponse;

src/main/java/cn/com/hellowood/dynamicdatasource/configuration/CustomHandlerExceptionResolver.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,17 @@ public ModelAndView resolveException(HttpServletRequest request, HttpServletResp
3232
CommonResponse commonResponse = new CommonResponse();
3333
if (handler instanceof HandlerMethod) {
3434
HandlerMethod handlerMethod = (HandlerMethod) handler;
35-
36-
if (ex instanceof ServiceException) {//Service exception,handler exception from service
35+
//Service exception,handler exception from service
36+
if (ex instanceof ServiceException) {
3737
commonResponse.setCode(ResponseCode.SUCCESS).setMessage(ex.getMessage());
3838
logger.warn(ex.getMessage());
3939
} else {
40-
41-
if (ex instanceof DataAccessException) {//DB exception
40+
//DB exception
41+
if (ex instanceof DataAccessException) {
4242
commonResponse.setCode(ResponseCode.INTERNAL_SERVER_ERROR)
4343
.setMessage(CommonConstant.DB_ERROR_MESSAGE);
44-
} else {//Others exception
44+
} else {
45+
//Others exception
4546
commonResponse.setCode(ResponseCode.INTERNAL_SERVER_ERROR)
4647
.setMessage(CommonConstant.SERVER_ERROR_MESSAGE);
4748
}

src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DataSourceConfigurer.java

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package cn.com.hellowood.dynamicdatasource.configuration;
22

3+
import cn.com.hellowood.dynamicdatasource.common.DataSourceKey;
34
import org.mybatis.spring.SqlSessionFactoryBean;
45
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
56
import org.springframework.boot.context.properties.ConfigurationProperties;
67
import org.springframework.context.annotation.Bean;
78
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.context.annotation.Primary;
10+
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
11+
import org.springframework.transaction.PlatformTransactionManager;
812

913
import javax.sql.DataSource;
1014
import java.util.HashMap;
@@ -26,19 +30,42 @@ public class DataSourceConfigurer {
2630
* @return data source
2731
*/
2832
@Bean("master")
33+
@Primary
2934
@ConfigurationProperties(prefix = "application.server.db.master")
3035
public DataSource master() {
3136
return DataSourceBuilder.create().build();
3237
}
3338

3439
/**
35-
* slave DataSource
40+
* Slave alpha data source.
3641
*
37-
* @return data source
42+
* @return the data source
43+
*/
44+
@Bean("slaveAlpha")
45+
@ConfigurationProperties(prefix = "application.server.db.slave-alpha")
46+
public DataSource slaveAlpha() {
47+
return DataSourceBuilder.create().build();
48+
}
49+
50+
/**
51+
* Slave beta data source.
52+
*
53+
* @return the data source
3854
*/
39-
@Bean("slave")
40-
@ConfigurationProperties(prefix = "application.server.db.slave")
41-
public DataSource slave() {
55+
@Bean("slaveBeta")
56+
@ConfigurationProperties(prefix = "application.server.db.slave-beta")
57+
public DataSource slaveBeta() {
58+
return DataSourceBuilder.create().build();
59+
}
60+
61+
/**
62+
* Slave gamma data source.
63+
*
64+
* @return the data source
65+
*/
66+
@Bean("slaveGamma")
67+
@ConfigurationProperties(prefix = "application.server.db.slave-gamma")
68+
public DataSource slaveGamma() {
4269
return DataSourceBuilder.create().build();
4370
}
4471

@@ -50,9 +77,11 @@ public DataSource slave() {
5077
@Bean("dynamicDataSource")
5178
public DataSource dynamicDataSource() {
5279
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
53-
Map<Object, Object> dataSourceMap = new HashMap<>(2);
54-
dataSourceMap.put("master", master());
55-
dataSourceMap.put("slave", slave());
80+
Map<Object, Object> dataSourceMap = new HashMap<>(4);
81+
dataSourceMap.put(DataSourceKey.master.name(), master());
82+
dataSourceMap.put(DataSourceKey.slaveAlpha.name(), slaveAlpha());
83+
dataSourceMap.put(DataSourceKey.slaveBeta.name(), slaveBeta());
84+
dataSourceMap.put(DataSourceKey.slaveGamma.name(), slaveGamma());
5685

5786
// Set master datasource as default
5887
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
@@ -61,6 +90,10 @@ public DataSource dynamicDataSource() {
6190

6291
// To put datasource keys into DataSourceContextHolder to judge if the datasource is exist
6392
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
93+
94+
// To put slave datasource keys into DataSourceContextHolder to load balance
95+
DynamicDataSourceContextHolder.slaveDataSourceKeys.addAll(dataSourceMap.keySet());
96+
DynamicDataSourceContextHolder.slaveDataSourceKeys.remove(DataSourceKey.master.name());
6497
return dynamicRoutingDataSource;
6598
}
6699

@@ -83,5 +116,15 @@ public SqlSessionFactoryBean sqlSessionFactoryBean() {
83116
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
84117
return sqlSessionFactoryBean;
85118
}
119+
120+
/**
121+
* Transaction manager platform transaction manager.
122+
*
123+
* @return the platform transaction manager
124+
*/
125+
@Bean
126+
public PlatformTransactionManager transactionManager() {
127+
return new DataSourceTransactionManager(dynamicDataSource());
128+
}
86129
}
87130

src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceAspect.java

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import org.aspectj.lang.annotation.After;
55
import org.aspectj.lang.annotation.Aspect;
66
import org.aspectj.lang.annotation.Before;
7+
import org.aspectj.lang.annotation.Pointcut;
78
import org.slf4j.Logger;
89
import org.slf4j.LoggerFactory;
9-
import org.springframework.core.annotation.Order;
1010
import org.springframework.stereotype.Component;
1111

1212
/**
@@ -17,39 +17,60 @@
1717
* @email hellowoodes@gmail.com
1818
*/
1919
@Aspect
20-
@Order(-1) // To ensure execute before @Transactional
2120
@Component
2221
public class DynamicDataSourceAspect {
2322
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
2423

24+
private final String[] QUERY_PREFIX = {"select"};
25+
26+
/**
27+
* Dao aspect.
28+
*/
29+
@Pointcut("execution( * cn.com.hellowood.dynamicdatasource.mapper.*.*(..))")
30+
public void daoAspect() {
31+
}
32+
2533
/**
2634
* Switch DataSource
2735
*
28-
* @param point
29-
* @param targetDataSource
36+
* @param point the point
3037
*/
31-
@Before("@annotation(targetDataSource))")
32-
public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) {
33-
if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value())) {
34-
logger.error("DataSource [{}] doesn't exist, use default DataSource [{}]", targetDataSource.value());
35-
} else {
36-
DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value());
37-
logger.info("Switch DataSource to [{}] in Method [{}]",
38+
@Before("daoAspect()")
39+
public void switchDataSource(JoinPoint point) {
40+
Boolean isQueryMethod = isQueryMethod(point.getSignature().getName());
41+
if (isQueryMethod) {
42+
DynamicDataSourceContextHolder.useSlaveDataSource();
43+
logger.debug("Switch DataSource to [{}] in Method [{}]",
3844
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
3945
}
4046
}
4147

4248
/**
4349
* Restore DataSource
4450
*
45-
* @param point
46-
* @param targetDataSource
51+
* @param point the point
4752
*/
48-
@After("@annotation(targetDataSource))")
49-
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
53+
@After("daoAspect())")
54+
public void restoreDataSource(JoinPoint point) {
5055
DynamicDataSourceContextHolder.clearDataSourceKey();
51-
logger.info("Restore DataSource to [{}] in Method [{}]",
56+
logger.debug("Restore DataSource to [{}] in Method [{}]",
5257
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
5358
}
5459

60+
61+
/**
62+
* Judge if method start with query prefix
63+
*
64+
* @param methodName
65+
* @return
66+
*/
67+
private Boolean isQueryMethod(String methodName) {
68+
for (String prefix : QUERY_PREFIX) {
69+
if (methodName.startsWith(prefix)) {
70+
return true;
71+
}
72+
}
73+
return false;
74+
}
75+
5576
}

src/main/java/cn/com/hellowood/dynamicdatasource/configuration/DynamicDataSourceContextHolder.java

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package cn.com.hellowood.dynamicdatasource.configuration;
22

33

4+
import cn.com.hellowood.dynamicdatasource.common.DataSourceKey;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
48
import java.util.ArrayList;
59
import java.util.List;
10+
import java.util.concurrent.locks.Lock;
11+
import java.util.concurrent.locks.ReentrantLock;
612

713
/**
814
* Multiple DataSource Context Holder
@@ -13,30 +19,61 @@
1319
*/
1420
public class DynamicDataSourceContextHolder {
1521

16-
// private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
22+
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
23+
24+
private static Lock lock = new ReentrantLock();
25+
26+
private static int counter = 0;
1727

1828
/**
1929
* Maintain variable for every thread, to avoid effect other thread
2030
*/
21-
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
22-
@Override
23-
protected String initialValue() {
24-
return "master";
25-
}
26-
};
31+
private static final ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.master::name);
32+
2733

2834
/**
2935
* All DataSource List
3036
*/
3137
public static List<Object> dataSourceKeys = new ArrayList<>();
3238

39+
/**
40+
* The constant slaveDataSourceKeys.
41+
*/
42+
public static List<Object> slaveDataSourceKeys = new ArrayList<>();
43+
3344
/**
3445
* To switch DataSource
3546
*
3647
* @param key the key
3748
*/
3849
public static void setDataSourceKey(String key) {
39-
contextHolder.set(key);
50+
CONTEXT_HOLDER.set(key);
51+
}
52+
53+
/**
54+
* Use master data source.
55+
*/
56+
public static void useMasterDataSource() {
57+
CONTEXT_HOLDER.set(DataSourceKey.master.name());
58+
}
59+
60+
/**
61+
* Use slave data source.
62+
*/
63+
public static void useSlaveDataSource() {
64+
lock.lock();
65+
66+
try {
67+
int datasourceKeyIndex = counter % slaveDataSourceKeys.size();
68+
CONTEXT_HOLDER.set(String.valueOf(slaveDataSourceKeys.get(datasourceKeyIndex)));
69+
counter++;
70+
} catch (Exception e) {
71+
logger.error("Switch slave datasource failed, error message is {}", e.getMessage());
72+
useMasterDataSource();
73+
e.printStackTrace();
74+
} finally {
75+
lock.unlock();
76+
}
4077
}
4178

4279
/**
@@ -45,14 +82,14 @@ public static void setDataSourceKey(String key) {
4582
* @return data source key
4683
*/
4784
public static String getDataSourceKey() {
48-
return contextHolder.get();
85+
return CONTEXT_HOLDER.get();
4986
}
5087

5188
/**
5289
* To set DataSource as default
5390
*/
5491
public static void clearDataSourceKey() {
55-
contextHolder.remove();
92+
CONTEXT_HOLDER.remove();
5693
}
5794

5895
/**

0 commit comments

Comments
 (0)