数据源动态切换也不是什么新技术,阿里在早期都有多隆大神实现了。但是我们今天要讲的是 Spring 对数据源路由的实现。
大项目的多个数据库动态切换已是架构师考虑的趋势。数据源动态切换往往能给我带来很多好处,比如根据多语言实现数据库动态切换,读写分离数据库动态切换,灾备处理等都可以应用数据源的动态切换。
除了上面的应用,关于水平分库和垂直分库,都是多数据源的切换的使用场景。关于数据库架构方面的知识,推荐大家阅读我的这篇文章:大型网站应用中MySQL的架构演变史。
下面回归今天的主题,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;*/ } //设置数据源 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" 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="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="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" class="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.*.*(..))"/> <!-- 关键配置,切换数据源一定要比持久层代码更先执行(事务也算持久层代码) --> <aop:advisor advice-ref="txAdvice" pointcut-ref="service" order="2"/> <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="service" order="1"/> </aop:config> </beans>
以上核心代码就可以让我们基于aop实现多语言,或者读写分离之类的多数据源切换了。
这篇文章只是介绍了 AbstractRoutingDataSource 的简陋实现,关于读写分离和 AbstractRoutingDataSource 的实现原理,我们后边在实现。
: » Spring动态路由AbstractRoutingDataSource(数据源动态切换)教程
原创文章,作者:端木书台,如若转载,请注明出处:https://blog.ytso.com/251682.html