【Mysql】动态数据源切换和分布式事务

一、动态数据源切换

    再谈到动态数据源切换之前,我们先看一下常规的单数据库连接池(数据源)的配置过程,我们以Hikari连接池(Hikari官网-高效连接池)和Mybatis框架为例说明。

①Spring Mybatis Hikari datasource 配置


    @Bean(name = "userReadHikariConfig")
    public HikariConfig userReadHikariConfig() {
        HikariConfig conf = new HikariConfig();
        conf.setJdbcUrl(readUrl);
        conf.setUsername(userName);
        conf.setPassword(password);
        conf.setPoolName("user-read-datasource");
        conf.setConnectionTimeout(connectTimeout);
        conf.setValidationTimeout(validateTimeout);
        conf.setMaximumPoolSize(maximumPoolSize);
        return conf;
    }

    @Bean(name = "userReadDataSource")
    public DataSource userReadDataSource() {
        return new HikariDataSource(userReadHikariConfig());
    }

    @Bean(name = "userReadSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(userReadDataSource());
sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return sqlSessionFactoryBean.getObject();
    }

如上所示,我们配置了一个用户数据库读库的连接池(一些变量赋值缺省),并构造注入了SqlSessionFactory。

SqlSessionFactory是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像。SqlSessionFactory一旦被创建,应该在应用执行期间都存在,它是创建SqlSession的工厂。

SqlSession也是MyBatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection。它完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句。每个线程都应该有它自己的SqlSession实例,SqlSession的实例不能被共享,同时SqlSession也是线程不安全的。

②AbstractRoutingDatasource

      单个数据源配置清楚了,多个数据源只需要重复上述配置即可,如果我们需要在业务读写分离,我们如何做到动态切换数据源呢,Spring为我们提供的抽象类AbstractRoutingDatasource实现了DataSource接口的用于获取数据库连接的方法,通过AOP的方式在程序运行时动态切换数据源。

è¿éåå¾çæè¿°

具体来说怎么实现呢,首先编写AbstractRoutingDataSource的实现类DynamicDataSource,根据当前线程来选择数据源,然后通过AOP拦截特定的注解,设置当前的数据源信息。

public class DynamicDatasource extends AbstractRoutingDataSource {

    private static class DynamicDataSourceHolder {

        //使用ThreadLocal来保存当前线程需要使用的 dataSource 的 key。保证线程安全
        private static final ThreadLocal<String> holder = new ThreadLocal<>();

        private static void setDataSourceKey(String key) {
            holder.set(key);
        }

        private static String getDataSourceKey() {
            String key = holder.get();
            if (null == key) {
                key = DataSourceTypeEnum.READ.getName();   //默认写库
            }
            return key;
        }

    }

    public static void setWrite() {
        DynamicDataSourceHolder.setDataSourceKey(DataSourceTypeEnum.WRITE.getName());
    }

    public static void setRead() {
        DynamicDataSourceHolder.setDataSourceKey(DataSourceTypeEnum.READ.getName());
    }


    @Override
    protected Object determineCurrentLookupKey() {
        // 一个读库实现。多个读库时可以在这里做负载均衡
        return DynamicDataSourceHolder.getDataSourceKey();
    }

    //这个函数看似没用,在后面分布式事务的时候再说明
    public String getCurrentDatasourceKey()
    {
        return DynamicDataSourceHolder.getDataSourceKey();
    }

    public enum DataSourceTypeEnum {
        WRITE("write"),
        READ("read");

        private String name;

        DataSourceTypeEnum(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}

以上核心是重写determineCurrentLookupKey这个方法,可以看到它的实现是通过getDataSourceKey获取threadlocal里保存的key,而在AbstractRoutingDataSource 里通过key和datasource的map,找到对应的datasource。感兴趣的同学可以看一下AbstractRoutingDataSource这个类的源码,这里不在粘贴详述。

DynamicDataSource编写完成之后,我们用它来替换①中SqlSessionFactory构造是直接使用的HikariDatasource,具体代码如下:


    @Bean(name = "userReadHikariConfig")
    public HikariConfig userReadHi
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值