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

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

更多精彩内容请看 web前端中文站
www.lisa33xiaoq.net 可按Ctrl + D 进行收藏

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

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

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

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

 package com.lisa33xiaoq.net.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.lisa33xiaoq.net.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.lisa33xiaoq.net.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.lisa33xiaoq.net.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?的实现原理,我们后边在实现。

【注:本文源自网络文章资源,由站长整理发布】

0
如无特殊说明,文章均为原作者原创,转载请注明出处

该文章由 发布

这货来去如风,什么鬼都没留下!!!
发表我的评论

Hi,请填写昵称和邮箱!

取消评论
代码 贴图 加粗 链接 删除线 签到