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

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