spring手写读写分离

一,原理

  利用spring提供的AbstractRoutingDataSource的determineCurrentLookupKey,lookupkey路由方法决定DataSource。

二,步骤

准备拦截器,用于拦截mybatis的请求

 1 @Intercepts({@Signature(type = Executor.class,method = "update", args = {MappedStatement.class,Object.class}),
 2             @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
 3 @Component
 4 @Slf4j
 5 public class DynamicDataSourceInterceptor implements Interceptor {
 6 
 7     private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
 8 
 9     @Override
10     public Object intercept(Invocation invocation) throws Throwable {
11         boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
12         String lookupKey;
13         if (!synchronizationActive) {
14             //如果是非事务的,判断读或者写
15             //获取SQL中的参数
16             Object[] objects = invocation.getArgs();
17             //objects[0]中携带增删改查信息,可以判断是读还是写
18             MappedStatement ms = (MappedStatement) objects[0];
19             //如果是读,且为自增id查询主键,则使用主库,这种判断主要用于插入时返回id的操作,由于日志同步到从库有延时,
20             //所以如果插入时需要返回id,则不适合从库查询数据库,有可能查询不到。
21             if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)
22                     && ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
23                 lookupKey = DynamicDataSourceHolder.DB_MASTER;
24             }else {
25                 BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
26                 String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\\t\\n\\r]"," ");
27                 if (sql.matches(REGEX)){
28                     //写语句
29                     lookupKey = DynamicDataSourceHolder.DB_MASTER;
30                 }else {
31                     lookupKey = DynamicDataSourceHolder.DB_SLAVE;
32                 }
33             }
34         }else {
35             lookupKey = DynamicDataSourceHolder.DB_MASTER;
36         }
37         log.info("在{}中进行操作",lookupKey);
38         DynamicDataSourceHolder.setDbType(lookupKey);
39         return invocation.proceed();
40     }
41 
42     @Override
43     public Object plugin(Object o) {
44         if (o instanceof Executor) {
45             return Plugin.wrap(o, this);
46         }
47         return o;
48     }
49 
50     @Override
51     public void setProperties(Properties properties) {
52 
53     }
54 }

2,利用ThreadLocal,实现DynamicDataSourceHolder工具类

 1 @Slf4j
 2 public class DynamicDataSourceHolder {
 3     private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
 4     public static final String DB_MASTER = "master";
 5     public static final String DB_SLAVE = "slave";
 6 
 7 
 8     public static String getDbType() {
 9         String db = contextHolder.get();
10         if (StringUtils.isEmpty(db)) {
11             db = "master";
12         }
13         return db;
14     }
15 
16     public static void setDbType(String str) {
17         log.info("所使用的数据源为{}", str);
18         contextHolder.set(str);
19     }
20 
21     public static void clearDbType() {
22         contextHolder.remove();
23     }

3,读写分离的关键类

 1 public class DynamicDataSource extends AbstractRoutingDataSource {
 2     @Resource(name = "masterDataSource")
 3     private DataSource masterDataSource;
 4     @Resource(name = "slaveDataSource")
 5     private DataSource slaveDataSource;
 6 
 7     @Override
 8     public void afterPropertiesSet() {
 9         setDefaultTargetDataSource(slaveDataSource);
10         Map<Object, Object> dataSourceMap = new HashMap<>();
11         dataSourceMap.put("master", masterDataSource);
12         dataSourceMap.put("slave", slaveDataSource);
13         setTargetDataSources(dataSourceMap);
14         super.afterPropertiesSet();
15     }
16 
17     @Override
18     protected Object determineCurrentLookupKey() {
19         return DynamicDataSourceHolder.getDbType();
20     }
21 }

4,配置DataSource

 1 @Configuration
 2 @Slf4j
 3 public class DataSourceConfig {
 4     @Value("${datasource1.mysql.url}")
 5     String masterUrl;
 6     @Value("${datasource1.mysql.username}")
 7     String masterName;
 8     @Value("${datasource1.mysql.password}")
 9     String masterPsw;
10     @Value("${datasource2.mysql.url}")
11     String slaveUrl;
12     @Value("${datasource2.mysql.username}")
13     String slaveName;
14     @Value("${datasource2.mysql.password}")
15     String slavePsw;
16 
17     @Bean(name = "masterDataSource")
18     public DataSource masterProperties(){
19         log.info("masterDataSource初始化");
20         HikariDataSource dataSource = new HikariDataSource();
21         dataSource.setJdbcUrl(masterUrl);
22         dataSource.setUsername(masterName);
23         dataSource.setPassword(masterPsw);
24         return dataSource;
25     }
26 
27     @Bean(name = "slaveDataSource")
28     public DataSource slaveProperties(){
29         log.info("slaveDataSource初始化");
30         HikariDataSource dataSource = new HikariDataSource();
31         dataSource.setJdbcUrl(slaveUrl);
32         dataSource.setUsername(slaveName);
33         dataSource.setPassword(slavePsw);
34         return dataSource;
35     }
36 
37     @Bean
38     @Primary
39     public AbstractRoutingDataSource routingDataSource(){
40         return new DynamicDataSource();
41     }
42 }

这里一定要有@primary,让routingDataSource是spring的DataSource首选注入对象,初始化DynamicDataSource。

三,结论

这样就通过拦截器实现对mybatis的拦截,就不用在业务代码里修改,本方法使用就局限性,按需使用

posted @ 2020-03-03 18:28  superChong  阅读(302)  评论(0编辑  收藏  举报