在spring中有一個AbstractRoutingDataSource抽象類可以實現(xiàn)數(shù)據(jù)源的路由,核心方法是determineTargetDataSource,注釋的大概意思通過determineCurrentLookupKey()確定key,在targetDataSources中通過key檢索對應的數(shù)據(jù)源。

determineCurrentLookupKey()是一個抽象方法,需要我們來實現(xiàn),返回我們要的key,targetDataSources是一個map,保存數(shù)據(jù)源

我們實現(xiàn)的思路是,項目啟動時就讓spring保存我們所有的數(shù)據(jù)源,通過mybatis的攔截器攔截方法,判斷是應該選擇主數(shù)據(jù)庫還是從數(shù)據(jù)庫然后返回對應的key。
一. 先實現(xiàn)這個數(shù)據(jù)源切換
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("數(shù)據(jù)源類型:{}", type);
dataSourceHolder.set(type);
}
public static void clear() {
dataSourceHolder.remove();
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<String, DataSource> targetDataSources) {
// 調用super方法保存數(shù)據(jù)源
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(new HashMap<>(targetDataSources));
// 啟動時加載
super.afterPropertiesSet();
}
/**
* 要實現(xiàn)的方法,返回key
* 通過DynamicDataSourceHolder保存key
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.get();
}
}
二、實現(xiàn)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) {
}
}
三、數(shù)據(jù)源配置和配置類
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主從同步的配置,大家可以單獨修改掉從庫中的數(shù)據(jù)然后查詢看是否為從庫中修改后的數(shù)據(jù),是就說明大功告成!