在spring中有一个AbstractRoutingDataSource抽象类可以实现数据源的路由,核心方法是determineTargetDataSource,注释的大概意思通过determineCurrentLookupKey()确定key,在targetDataSources中通过key检索对应的数据源。
determineCurrentLookupKey()是一个抽象方法,需要我们来实现,返回我们要的key,targetDataSources是一个map,保存数据源
我们实现的思路是,项目启动时就让spring保存我们所有的数据源,通过mybatis的拦截器拦截方法,判断是应该选择主数据库还是从数据库然后返回对应的key。
一. 先实现这个数据源切换
public class DynamicDataSourceHolder {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceHolder.class);
private static ThreadLocal<String> dataSourceHolder = new ThreadLocal();
public static final String master = "master";
public static final String slave = "slave";
public static String get() {
String db = dataSourceHolder.get();
if(db == null) {
db = master;
}
return db;
}
public static void setDBType(String type) {
log.info("数据源类型:{}", type);
dataSourceHolder.set(type);
}
public static void clear() {
dataSourceHolder.remove();
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<String, DataSource> targetDataSources) {
// 调用super方法保存数据源
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(new HashMap<>(targetDataSources));
// 启动时加载
super.afterPropertiesSet();
}
/**
* 要实现的方法,返回key
* 通过DynamicDataSourceHolder保存key
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.get();
}
}
二、实现mybatis拦截器
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, })
})
@Slf4j
public class DynamicDataSourceInterceptor implements Interceptor {
// 正则匹配update等写语句
private static final String SQL_REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 是否是事务方法
boolean active = TransactionSynchronizationManager.isActualTransactionActive();
String lookupKey = DynamicDataSourceHolder.master;
//非事务
if(!active) {
Object[] args = invocation.getArgs();
// mybatis statment 就是mapper xml文件中的select标签
MappedStatement mappedStatements = (MappedStatement) args[0];
// SqlCommandType:select、update、insert、delete
log.info("SqlCommandType:{}", mappedStatements.getSqlCommandType());
// 可以获取到sql语句
log.info("Sql:{}", mappedStatements.getSqlSource().getBoundSql(args[1]).getSql());
/*
* 如果是select语句
* */
if(mappedStatements.getSqlCommandType().equals(SqlCommandType.SELECT)) {
if(mappedStatements.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
lookupKey = DynamicDataSourceHolder.master;
} else {
lookupKey = DynamicDataSourceHolder.slave;
}
// 非select
}else {
BoundSql sql = mappedStatements.getSqlSource().getBoundSql(args[1]);
String sqlStr = sql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
if(sqlStr.matches(SQL_REGEX)) {
lookupKey = DynamicDataSourceHolder.master;
} else {
lookupKey = DynamicDataSourceHolder.slave;
}
}
//事务方法走主库
} else {
lookupKey = DynamicDataSourceHolder.master;
}
DynamicDataSourceHolder.setDBType(lookupKey);
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
if(o instanceof Executor) {
return Plugin.wrap(o, this);
} else {
return o;
}
}
@Override
public void setProperties(Properties properties) {
}
}
三、数据源配置和配置类
spring:
datasource:
druid:
one:
url: jdbc:mysql://192.168.1.8:3306/cloud_alibaba_user_center?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: LyL@163.com
driver-class-name: com.mysql.cj.jdbc.Driver
two:
url: jdbc:mysql://192.168.1.9:3306/cloud_alibaba_user_center?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: LyL@163.com
driver-class-name: com.mysql.cj.jdbc.Driver
@Configuration
public class DataSourceConfiguration {
@Autowired
private DynamicDataSourceInterceptor dataSourceInterceptor;
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.addInterceptor(dataSourceInterceptor);
}
};
}
@Bean
@ConfigurationProperties("spring.datasource.druid.one")
public DataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.two")
public DataSource slaveDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dataSource() {
Map<String, DataSource> targetDataSources = new HashMap<>();
targetDataSources.put(DynamicDataSourceHolder.master, masterDataSource());
targetDataSources.put(DynamicDataSourceHolder.slave, slaveDataSource());
DynamicDataSource dynamicDataSource = new DynamicDataSource(masterDataSource(), targetDataSources);
return dynamicDataSource;
}
}
四、启动类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@Import({DataSourceConfiguration.class})
@MapperScan(basePackages = "com.yonglong.bookhouse.user.mapper")
public class UserCenterApplication {
public static void main(String[] args) {
SpringApplication.run(UserCenterApplication.class, args);
}
}
测试可用,大家可自行测试,上一篇是mysql主从同步的配置,大家可以单独修改掉从库中的数据然后查询看是否为从库中修改后的数据,是就说明大功告成!