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的拦截,就不用在业务代码里修改,本方法使用就局限性,按需使用