Mybatis(攔截器實現)通用mapper及全ORM實現(五)-- springboot+mybatis多資料來源設定
阿新 • • 發佈:2018-12-09
本篇實際上和mybatisext專案並沒有太大關係了,但在實際專案中脫離不開多個數據源,尤其是主從分離,同樣網上一些資料大同小異而且大部分並不能真正解決問題,所以單獨提出來說一下
假設我們就是要解決一個主從分離,資料來源定義在了application.properties中如下:
datasources.master.driverClassName=com.mysql.cj.jdbc.Driver datasources.master.url=xxx datasources.master.username=xxx datasources.master.password=xxx datasources.master.type=com.alibaba.druid.pool.DruidDataSource datasources.master.filters=stat datasources.master.maxActive= 20 datasources.master.initialSize= 1 datasources.master.maxWait= 60000 datasources.master.minIdle =1 datasources.master.timeBetweenEvictionRunsMillis= 60000 datasources.master.minEvictableIdleTimeMillis=300000 datasources.master.validationQuery= select 'x' datasources.master.testWhileIdle= true datasources.master.testOnBorrow=false datasources.master.testOnReturn= false datasources.master.poolPreparedStatements=true datasources.master.maxOpenPreparedStatements= 20 datasources.slave.driverClassName=com.mysql.cj.jdbc.Driver datasources.slave.url=xxx datasources.slave.username=xxx datasources.slave.password=xxx datasources.slave.type=com.alibaba.druid.pool.DruidDataSource datasources.slave.filters=stat datasources.slave.maxActive= 20 datasources.slave.initialSize= 1 datasources.slave.maxWait= 60000 datasources.slave.minIdle =1 datasources.slave.timeBetweenEvictionRunsMillis= 60000 datasources.slave.minEvictableIdleTimeMillis=300000 datasources.slave.validationQuery= select 'x' datasources.slave.testWhileIdle= true datasources.slave.testOnBorrow=false datasources.slave.testOnReturn= false datasources.slave.poolPreparedStatements=true datasources.slave.maxOpenPreparedStatements= 20
定義一個列舉類,針對這兩個資料來源
public enum DataSourceType {
Master,
Slave,
}
動態資料來源定義,保證執行緒安全引用了ThreadLocal類記錄當前使用的資料來源
public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(DataSourceType dataSourceType){ contextHolder.set(dataSourceType); } public static DataSourceType getDataSourceType() { return contextHolder.get(); } public static void clear(){ contextHolder.remove(); } @Override protected Object determineCurrentLookupKey(){ return DynamicDataSource.getDataSourceType(); } }
然後進行mybatis配置的注入
@Configuration @MapperScan({"cw.frame.mybatisext.test.mapper"}) public class MyBatisConfig { @Autowired private Environment environment; @Autowired private MySqlInterceptor mySqlInterceptor; @Bean public MySqlInterceptor mySqlInterceptor(){ return new MySqlInterceptor(); } @Bean public DataSource masterDataSource() throws Exception{ return DruidDataSourceFactory.createDataSource(this.getDataSourceProperty("datasources.master.")); } @Bean public DataSource slaveDataSource() throws Exception{ return DruidDataSourceFactory.createDataSource(this.getDataSourceProperty("datasources.slave.")); } /** * @Primary 該註解表示在同一個介面有多個實現類可以注入的時候,預設選擇哪一個,而不是讓@autowire註解報錯 * @Qualifier 根據名稱進行注入,通常是在具有相同的多個型別的例項的一個注入(例如有多個DataSource型別的例項) * @DependsOn 解決迴圈依賴問題,在系統自動建立datasource前建立指定資料來源 * @param masterDataSource * @param slaveDataSource * @return */ @Bean @Primary @DependsOn({ "masterDataSource", "slaveDataSource"}) public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource){ Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put(DataSourceType.Master, masterDataSource); dataSourceMap.put(DataSourceType.Slave, slaveDataSource); DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(dataSourceMap); dynamicDataSource.setDefaultTargetDataSource(masterDataSource); return dynamicDataSource; } /** * 根據資料來源建立SqlSessionFactory * mybatis攔截器需要手動設定一下 fb.setPlugins(new Interceptor[]{mySqlInterceptor}); * @param dynamicDataSource * @return * @throws Exception */ @Bean public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception{ SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); // 因為手動建立了SqlSessionFactory,所以需要手動設定攔截器 fb.setPlugins(new Interceptor[]{mySqlInterceptor}); fb.setDataSource(dynamicDataSource); return fb.getObject(); } /** * 配置事務管理器 * @param dataSource * @return * @throws Exception */ @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception{ return new DataSourceTransactionManager(dataSource); } private Properties getDataSourceProperty(String prefixKey){ String[] keys = { "driverClassName", "url", "username", "password", "type", "filters", "maxActive", "initialSize", "maxWait", "minIdle", "timeBetweenEvictionRunsMillis", "minEvictableIdleTimeMillis", "validationQuery", "testWhileIdle", "testOnBorrow", "testOnReturn", "poolPreparedStatements", "maxOpenPreparedStatements" }; Properties properties = new Properties(); for (String key : keys){ properties.put(key, environment.getProperty(prefixKey + key)); } return properties; } }
這裡特別說明一下: @DependsOn({ "masterDataSource", "slaveDataSource"}),告知springboot這個bean依賴另外兩個bean,保證了注入順序,要不會報錯或不起作用 另外由於手動建立了SqlSessionFactory,所以攔截器的定義也要手動設定一下(程式碼中已有註釋說明),要不按照普通的配置攔截器將無法生效
最後在做一個切片定義
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(DataSource)")
public void beforeExecute(JoinPoint point){
}
@Around("@annotation(DataSource)")
public Object aroundExecute(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
Class<?> classType = proceedingJoinPoint.getTarget().getClass();
String methodName = proceedingJoinPoint.getSignature().getName();
Class[] argClass = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterTypes();
DataSourceType lastDataSourceType = null;
boolean resetDataSourceType = false;
Method method = classType.getMethod(methodName, argClass);
if (method.isAnnotationPresent(DataSource.class)){
DataSourceType dataSourceType = method.getAnnotation(DataSource.class).value();
lastDataSourceType = DynamicDataSource.getDataSourceType();
if (lastDataSourceType == null){
DynamicDataSource.setDataSourceType(dataSourceType);
resetDataSourceType = true;
} else {
switch (lastDataSourceType){
case Master:
break;
case Slave:
if (dataSourceType == DataSourceType.Master){
DynamicDataSource.setDataSourceType(dataSourceType);
resetDataSourceType = true;
}
break;
}
}
}
Object result = proceedingJoinPoint.proceed();
if (resetDataSourceType){
if (lastDataSourceType == null){
DynamicDataSource.clear();
} else {
DynamicDataSource.setDataSourceType(lastDataSourceType);
}
}
return result;
}
@After("@annotation(DataSource)")
public void afterExecute(JoinPoint point){
}
}
所有帶@DataSource註解的都會進入切片,動態設定資料來源 當然程式碼中你想手動設定也可以通過:DynamicDataSource.setDataSourceType 方法進行設定
到此為止,介紹完了關於mybatis的攔截器全ORM實現的方案和mybatisext的專案,原始碼下載