1. 程式人生 > >Mybatis(攔截器實現)通用mapper及全ORM實現(五)-- springboot+mybatis多資料來源設定

Mybatis(攔截器實現)通用mapper及全ORM實現(五)-- springboot+mybatis多資料來源設定

本篇實際上和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的專案,原始碼下載