mybatits動態多數據源配置mysql 數據庫讀寫分離

數據庫讀寫分離

數據庫讀寫分離環境搭建:https://github.com/niezhiliang/mysql-master-slave-docker

數據庫讀寫分離一般分爲兩種,一種是靜態的,一種是動態的,顧名思義靜態是配置了不會變,有侷限性,而動態的會根據業務的需求自動切換數據源

靜態

這種方式一般適用於項目需要依賴兩個不同的數據庫,而不是所謂的讀寫分離的主從數據庫。比如一個數據庫沒有用戶表,他想用自己另一個項目中的
用戶,這個時候用這種方式就比較合適啦。要想用這種方式來配置數據庫的讀寫分離也不是不行,那就把讀寫業務拆分出來嘛。 生成兩套xml和mapper.java, 一套用來處理
寫操作,另一套用來進行讀操作。功能能實現,但在寫代碼的時候就特別煩,有時候讀的業務寫到寫的數據庫就尷尬啦。

這種方式比較簡單,就不做說明啦,相信我自己也不會忘的。?

動態

這種方式就比較適用於數據庫的讀寫分離啦。這種方式解決了靜態方式的缺陷,共用一套mapper文件,無需拆分讀寫業務,用到aop對mapper的方法進行分析,判斷是讀操作還是寫操作,
並切換到相應的數據源。架構搭建好以後,編碼的時候無需關心什麼讀跟寫,只需專注於業務實現就可以啦。

  • 配置好所有的讀寫數據源

    @Configuration
    public class DataBaseConfiguration {
    
        @Value("${spring.datasource.type}")
        private Class<? extends DataSource> dataSourceType;
    
        //這個就是我們的寫數據源,也就是master庫
        @Bean(name="writeDataSource", destroyMethod = "close", initMethod="init")
        @Primary
        @ConfigurationProperties(prefix = "spring.write.datasource")
        public DataSource writeDataSource() {
            return DataSourceBuilder.create()
                    .type(dataSourceType).build();
        }
        
        //配置第一個讀庫 所有讀數據源都需要將其配置成bean 並添加到讀數據源集合中
        @Bean(name = "readDataSourceOne")
        @ConfigurationProperties(prefix = "spring.read.one.datasource")
        public DataSource readDataSourceOne() {
            return DataSourceBuilder.create()
                    .type(dataSourceType).build();
        }
        
        //配置第二個讀庫
        @Bean(name = "readDataSourceTwo")
        @ConfigurationProperties(prefix = "spring.read.two.datasource")
        public DataSource readDataSourceTwo() {
            return DataSourceBuilder.create()
                    .type(dataSourceType).build();
        }
    
        //讀寫庫集合的Bean
        @Bean("readDataSources")
        public List<DataSource> readDataSources() {
            List<DataSource> dataSources = new ArrayList<DataSource>();
            dataSources.add(readDataSourceOne());
            dataSources.add(readDataSourceTwo());
            return dataSources;
        }
    }
    
  • 配置動態的sqlSessionFactory

@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@MapperScan(basePackages = {"com.niezhiliang.db.read.write.separate.dynamic.type.mapper"})
public class TxxsbatisConfiguration {
    
    //我們這裏使用的是alibaba druid連接池
    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;

    //讀取配置的讀數據庫的數量(從庫)
    @Value("${spring.datasource.readSize}")
    private String dataSourceSize;

    //我們的寫數據庫,也就是主數據庫
    @Resource(name = "writeDataSource")
    private DataSource dataSource;
    
    //所有的讀數據源集合
    @Resource(name = "readDataSources")
    private List<DataSource> readDataSources;


    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(roundRobinDataSourceProxy());
        sqlSessionFactoryBean.setTypeAliasesPackage("com.niezhiliang.db.read.write.separate.dynamic.type.domain");
        //mapper.xml文件位置
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
        .getResources("classpath:mappers*//*.xml"));
        sqlSessionFactoryBean.getObject().getConfiguration()
                .setMapUnderscoreToCamelCase(true);

        return sqlSessionFactoryBean.getObject();
    }


    //配置動態數據源的路由對象
    @Bean
    public AbstractRoutingDataSource roundRobinDataSourceProxy() {
        TxxsAbstractRoutingDataSource proxy = new TxxsAbstractRoutingDataSource(readDataSources.size());
        Map<Object,Object> targetDataSourceSize = new HashMap<Object,Object>();
        targetDataSourceSize.put(dataSourceType.getTypeName(),dataSource);

        for (int i = 0; i < readDataSources.size(); i++) {
            targetDataSourceSize.put(i,readDataSources.get(i));
        }
        //如果沒有讀數據集 則默認爲寫數據庫
        proxy.setDefaultTargetDataSource(dataSource);
        //配置讀數據源
        proxy.setTargetDataSources(targetDataSourceSize);

        return proxy;
    }

}
  • 配置一個線程池,用於切換數據源(通過改變當前線程變量的方法,達到動態改變數據源)
public class DataSourceContextHolder {
    private static final ThreadLocal<String> local = new ThreadLocal<String>();

    public static ThreadLocal<String> getLocal() {
        return local;
    }

    /**
     * 讀可能是多個庫
     */
    public static void read() {
        local.set(DataSourceType.read.getType());
    }

    /**
     * 寫只有一個庫
     */
    public static void write() {
        local.set(DataSourceType.write.getType());
    }

    public static String getJdbcType() {
        return local.get();
    }
}
  • aop攔截所有的數據庫請求
@Aspect
@Component
public class DataSourceAop {

    public static final Logger logger = LoggerFactory.getLogger(DataSourceAop.class);
    
    //攔截mapper所有的讀操作,將數據源動態切換到讀數據源
    @Before("execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.select*(..)) or execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*..count*(..))")
    public void setReadDataSourceType() {
        DataSourceContextHolder.read();
        logger.info("dataSource切換到:Read");
    }
    //攔截mapper所有的寫操作,將數據源動態切換到寫數據源
    @Before(("execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.insert*(..)) or execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.update*(..)) or execution(* com.niezhiliang.db.read.write.separate.dynamic.type.mapper..*.delete*(..))"))
    public void setWriteDataSourceType() {
        DataSourceContextHolder.write();
        logger.info("dataSource切換到:Write");
    }
}
  • 動態數據源切換
public class TxxsAbstractRoutingDataSource extends AbstractRoutingDataSource {

    private  int dataSourceNumber;

    private AtomicInteger count = new AtomicInteger(0);


    public TxxsAbstractRoutingDataSource(int dataSourceNumber) {
        this.dataSourceNumber = dataSourceNumber;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        //獲取當前線程是進行讀還是寫
        String typeKey = DataSourceContextHolder.getJdbcType();
        if (typeKey.equals(DataSourceType.write.getType()))
            return DataSourceType.write.getType();
        // 讀 簡單負載均衡 count會一直累加,那後模讀數據源的數量 達到簡單的負載均衡
        int number = count.getAndAdd(1);
        int lookupKey = number % dataSourceNumber;
        return new Integer(lookupKey);
    }
}
                  

源碼:https://github.com/niezhiliang/db-read-write-separate

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章