Spring动态路由AbstractRoutingDataSource(数据源动态切换)教程

http://www.anbomei.com/b113/45af2401b349.html

数据源动态切换也不是什么新技术,阿里在早期都有多隆大神实现了。但是我们今天要讲的是 Spring 对数据源路由的实现。

大项目的多个数据库动态切换已是架构师考虑的趋势。数据源动态切换往往能给我http://www.anbomei.com/0277/7ec15b5d9760.html带来很多好处,比如根据多语言实现数据库动态切换,读写分离数据库动态切换,灾备处理等都可以应用数据源的动态切换。

除了上面的应用,关于水平分库和垂直分库,都是多数据源的切换的使用场景。关于数据库架构方面的知识,推荐大家阅读我的这篇文章:大型网站应用中Myhttp://www.anbomei.com/933f/d0f1236f30ce.htmlSQL的架构演变史。

下面回归今天的主题,Spring 的 AbstractRoutingDataSource 动态数据源(数据源切换)。

AbstractRoutingDataSource 的一个作用就是可以根据用户发起的不同请求去转换不同的数据源,比如根据用户的不同地区语言选择不同的数据库。要实现动态数据源我们必须要实现 AbstractRoutingDataSource。

package com.xttblog.dataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
//动态数据源(数据源切换)
public class DynamicDataSource extends AbstractRoutingDataSource {
    private final static Logger _log = LoggerFactory.getLogger(DynamicDataSource.class);
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = getDataSource();
        _log.info("当前操作使用的数据源:{}", dataSource);
        return dataSource;
        /**多语言的一个实例
        String language = LocaleContextHolder.getLocale().getLanguage();  
        System.out.println("Language obtained: "+ language);  
        return language;*/
    }
 http://www.anbomei.com/37bb/071db33f6b63.html;   //设置数据源
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }
    //获取数据源
    public static String getDataSource() {
        String dataSource = contextHolder.get();
        // 如果没有指定数据源,使用默认数据源
        if (null == dataSource) {
            DynamicDataSource.setDataSource(DataSourceEnum.MASTER.getDefault());
        }
        return contextHolder.get();
    }
    //清除数据源
    public static void clearDataSource() {
        contextHolder.remove();
    }
}

多个数据源是多个dataSource,切记不能是多个sqlSessionFactory。下面是 DynamicDataSource 多数据源的配置内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  &nbhttp://www.anbomei.com/eb38/7de5cdb90bf0.htmlsp;    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/tx
          http://www.springframework.org/schema/tx/spring-tx.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context.xsd
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 引入jdbc配置文件 -->
    <!--<context:property-placeholder location="classpath:jdbc.properties" />-->
    <!-- 配置进行解密  -->
    <bean id="propertyConfigurer" class="com.xttblog.plugin.EncryptPropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:jdbc.properties</value>
                <value>classpath:redis.properties</value>
            </list>
        </property>
    </bean>
    <!-- 主库数据源 -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="driverClassName" value="${master.jdbc.driver}"/>
        <property name="url" value="${master.jdbc.url}"/>
        <property name="username" value="${master.jdbc.username}"/>
        <property name="password" value="${master.jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="20"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <!-- 校验语句 -->
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>
    <!-- 从库数据源 -->
    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="driverClassName" value="${slave.jdbc.driver}"/>
        <property name="url" value="${slave.jdbc.url}"/>
        <property name="username" value="${slave.jdbc.username}"/>
        <property name="password" value="${slave.jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value=&quohttp://www.anbomei.com/421c/a262a97f9f95.htmlt;1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="20"/>
        <!-- 配置获取连接等待超时的时间 -->
      &nbhttp://www.anbomei.com/44a5/7e892d2d51cf.htmlsp; <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <!-- 校验语句 -->
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>
    <!-- 动态数据源 -->
    <bean id="dataSource" class="com.xttblog.dataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- 可配置多个数据源 -->
                <entry value-ref="masterDataSource" key="masterDataSource"></entry>
                <entry value-ref="slaveDataSource" key="slaveDataSource"></entry>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource"></property>
    </bean>
    <!-- 为Mybatis创建SqlSessionFactory,同时指定数据源 -->
    <bean id="sqlSessionFactory" classhttp://www.anbomei.com/b0aa/6735d5824ba7.html="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath*:com/zheng/pay/dao/mapper/*Mapper.xml"/>
    </bean>
    <!-- Mapper接口所在包名,Spring会自动查找其下的Mapper -->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="**.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
    <!-- 事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="add*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="update*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="modify*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="edit*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="del*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="save*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="send*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="search*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="count*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
 
    <aop:config>
        <aop:pointcut id="service" expression="execution(* com.xttblog.datasource..*.service.*.*(..))"/>
        <!--http://www.anbomei.com/dd81/1943c4c90848.html 关键配置,切换数据源一定要比持久层代码更先执行(事务也算持久层代码) -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="service" order="2"/>
   http://www.anbomei.com/987b/ed081337950e.html;     <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="service" order="1"/>
    </aop:config>
</beans>

以上核心代码就可以让我们基于aop实现多语言,或者读写分离之类的多数据源切换了。

这篇文章只是介绍了 AbstractRoutingDataSource 的简陋实现,关于读写分离和 AbstractRoutingDataSource 的实现原理,我们后边在实现。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!