版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012881904/article/details/77449710
AbstractRoutingDataSource动态数据源切换
上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他的业务的需求。一般的情况下我们都是使用SSH或者SSM框架进行处理我们的数据源的信息。
操作数据一般都是在DAO层进行处理,可以选择直接使用JDBC进行编程(http://blog.csdn.net/yanzi1225627/article/details/26950615/)
或者是使用多个DataSource 然后创建多个SessionFactory,在使用Dao层的时候通过不同的SessionFactory进行处理,不过这样的入侵性比较明显,一般的情况下我们都是使用继承HibernateSupportDao进行封装了的处理,如果多个SessionFactory这样处理就是比较的麻烦了,修改的地方估计也是蛮多的
最后一个,也就是使用AbstractRoutingDataSource的实现类通过AOP或者手动处理实现动态的使用我们的数据源,这样的入侵性较低,非常好的满足使用的需求。比如我们希望对于读写分离或者其他的数据同步的业务场景
下面看看图片
单数据源的场景(一般的Web项目工程这样配置进行处理,就已经比较能够满足我们的业务需求)
多数据源多SessionFactory这样的场景,估计作为刚刚开始想象想处理在使用框架的情况下处理业务,配置多个SessionFactory,然后在Dao层中对于特定的请求,通过特定的SessionFactory即可处理实现这样的业务需求,不过这样的处理带来了很多的不便之处,所有很多情况下我们宁愿直接使用封装的JDBC编程,或者使用Mybatis处理这样的业务场景
使用AbstractRoutingDataSource 的实现类,进行灵活的切换,可以通过AOP或者手动编程设置当前的DataSource,不用修改我们编写的对于继承HibernateSupportDao的实现类的修改,这样的编写方式比较好,至于其中的实现原理,让我细细到来。我们想看看如何去应用,实现原理慢慢的说!
编写AbstractRoutingDataSource的实现类,HandlerDataSource就是提供给我们动态选择数据源的数据的信息,我们这里编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解,设置当前的数据源信息,也可以手动的设置当前的数据源,在编程的类中。
1 package com.common.utils.manydatasource; 2 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 4 5 /** 6 * descrption: 多数据源的选择 7 * authohr: wangji 8 * date: 2017-08-21 10:32 9 */ 10 public class MultipleDataSourceToChoose extends AbstractRoutingDataSource { 11 12 /** 13 * @desction: 根据Key获取数据源的信息,上层抽象函数的钩子 14 * @author: wangji 15 * @date: 2017/8/21 16 * @param: 17 * @return: 18 */ 19 @Override 20 protected Object determineCurrentLookupKey() { 21 return HandlerDataSource.getDataSource(); 22 } 23 }
设置动态选择的Datasource,这里的Set方法可以留给AOP调用,或者留给我们的具体的Dao层或者Service层中手动调用,在执行SQL语句之前
1 package com.common.utils.manydatasource; 2 3 /** 4 * descrption: 根据当前线程来选择具体的数据源 5 * authohr: wangji 6 * date: 2017-08-21 10:36 7 */ 8 public class HandlerDataSource { 9 10 private static ThreadLocal<String> handlerThredLocal = new ThreadLocal<String>(); 11 12 /** 13 * @desction: 提供给AOP去设置当前的线程的数据源的信息 14 * @author: wangji 15 * @date: 2017/8/21 16 * @param: [datasource] 17 * @return: void 18 */ 19 public static void putDataSource(String datasource) { 20 handlerThredLocal.set(datasource); 21 } 22 23 /** 24 * @desction: 提供给AbstractRoutingDataSource的实现类,通过key选择数据源 25 * @author: wangji 26 * @date: 2017/8/21 27 * @param: [] 28 * @return: java.lang.String 29 */ 30 public static String getDataSource() { 31 return handlerThredLocal.get(); 32 } 33 34 /** 35 * @desction: 使用默认的数据源 36 */ 37 public static void clear() { 38 handlerThredLocal.remove(); 39 } 40 } 41
设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上,dataSource是当前数据源的一个别名用于标识我们的数据源的信息。
1 package com.common.utils.manydatasource; 2 3 import java.lang.annotation.*; 4 5 /** 6 * @description: 创建拦截设置数据源的注解 7 * Created by wangji on 2017/8/21. 8 */ 9 @Target({ElementType.METHOD,ElementType.TYPE}) 10 @Retention(RetentionPolicy.RUNTIME) 11 @Documented 12 public @interface DynamicSwitchDataSource { 13 14 String dataSource() default ""; 15 }
AOP拦截类的实现,通过拦截上面的注解,在其执行之前处理设置当前执行SQL的数据源的信息,HandlerDataSource.putDataSource(….),这里的数据源信息从我们设置的注解上面获取信息,如果没有设置就是用默认的数据源的信息。
1 package com.common.utils.manydatasource; 2 3 import lombok.extern.slf4j.Slf4j; 4 import org.aspectj.lang.JoinPoint; 5 import org.aspectj.lang.annotation.After; 6 import org.aspectj.lang.annotation.Aspect; 7 import org.aspectj.lang.annotation.Before; 8 import org.aspectj.lang.annotation.Pointcut; 9 import org.aspectj.lang.reflect.MethodSignature; 10 import org.springframework.core.annotation.Order; 11 import org.springframework.stereotype.Component; 12 13 import java.lang.reflect.Method; 14 15 /** 16 * descrption: 使用AOP拦截特定的注解去动态的切换数据源 17 * authohr: wangji 18 * date: 2017-08-21 10:42 19 */ 20 @Aspect 21 @Slf4j 22 @Component 23 @Order(1) 24 public class HandlerDataSourceAop { 25 //@within在类上设置 26 //@annotation在方法上进行设置 27 @Pointcut("@within(com.common.utils.manydatasource.DynamicSwitchDataSource)||@annotation(com.common.utils.manydatasource.DynamicSwitchDataSource)") 28 public void pointcut() {} 29 30 @Before("pointcut()") 31 public void doBefore(JoinPoint joinPoint) 32 { 33 Method method = ((MethodSignature)joinPoint.getSignature()).getMethod(); 34 DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);//获取方法上的注解 35 if(annotationClass == null){ 36 annotationClass = joinPoint.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);//获取类上面的注解 37 if(annotationClass == null) return; 38 } 39 //获取注解上的数据源的值的信息 40 String dataSourceKey = annotationClass.dataSource(); 41 if(dataSourceKey !=null){ 42 //给当前的执行SQL的操作设置特殊的数据源的信息 43 HandlerDataSource.putDataSource(dataSourceKey); 44 } 45 log.info("AOP动态切换数据源,className"+joinPoint.getTarget().getClass().getName()+"methodName"+method.getName()+";dataSourceKey:"+dataSourceKey==""?"默认数据源":dataSourceKey); 46 } 47 48 @After("pointcut()") 49 public void after(JoinPoint point) { 50 //清理掉当前设置的数据源,让默认的数据源不受影响 51 HandlerDataSource.clear(); 52 } 53 54 }
配置数据源在Spring 核心容器中配置
1 jdbc.driver=com.mysql.jdbc.Driver 2 jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis 3 jdbc.username=root 4 jdbc.password=root 5 jdbc2.url=jdbc:mysql://127.0.0.1:3306/datasource2 6 7 <!-- 配置数据源 --> 8 <bean id="dataSource0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init"> 9 <property name="driverClassName" value="${jdbc.driver}"/> 10 <property name="url" value="${jdbc.url}"/> 11 <property name="username" value="${jdbc.username}"/> 12 <property name="password" value="${jdbc.password}"/> 13 <property name="maxActive" value="10"/> 14 </bean> 15 <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init"> 16 <property name="driverClassName" value="${jdbc.driver}"/> 17 <property name="url" value="${jdbc2.url}"/> 18 <property name="username" value="${jdbc.username}"/> 19 <property name="password" value="${jdbc.password}"/> 20 <property name="maxActive" value="10"/> 21 </bean>
配置之前我们实现的数据源选择的中间层AbstractRoutingDataSource的实现类,这里的key就是数据源信息的别名,通过这个key可以选择到数据源的信息。MultipleDataSourceToChoose就是上面写的数据源选择器的实现类
1 bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true"> 2 <description>数据源</description> 3 <property name="targetDataSources"> 4 <map key-type="java.lang.String" value-type="javax.sql.DataSource"> 5 <entry key="datasource0" value-ref="dataSource0" /> 6 <entry key="datasource1" value-ref="dataSource1" /> 7 </map> 8 </property> 9 <!-- 设置默认的目标数据源 --> 10 <property name="defaultTargetDataSource" ref="dataSource0" /> 11 </bean>
SessionFactory的配置还是照旧,使用以前的配置,只不过当前选择的数据源是datasource,也就是数据源选择的中间层MultipleDataSourceToChoose,因为当前的中间层中实现了DataSource这个接口,所以可以看做为DataSource的是实现类啦,所以配置不会出现问题。
1 <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 2 <property name="dataSource" ref="dataSource"/> 3 <!--指定Hibernate属性 --> 4 <property name="hibernateProperties"> 5 <props> 6 <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> 7 <prop key="hibernate.show_sql">false</prop> 8 <prop key="hibernate.format_sql">false</prop> 9 <prop key="hibernate.hbm2ddl.auto">update</prop> 10 <prop key="hibernate.autoReconnect">true</prop> 11 <prop key="hibernate.jdbc.batch_size">50</prop> 12 <prop key="hibernate.connection.autocommit">false</prop> 13 <prop key="hibernate.connection.release_mode">after_transaction</prop> 14 <prop key="hibernate.bytecode.use_reflection_optimizer">false</prop> 15 </props> 16 </property> 17 <property name="packagesToScan"> 18 <list> 19 <value>com.module</value> 20 </list> 21 </property> 22 </bean>
简单的使用AOP进行测试一下,这里测试的结果时不同的,所以是生效的,使用了不同的数据源,但是底层的实现没有进行任何的修改处理。
1 @Service 2 @Slf4j 3 public class UserInfoService implements IUserInfoService { 4 5 6 @Resource 7 private UserDao userDao; 8 @Autowired 9 private CommonHibernateDao commonDao; 10 11 @TestValidateParam 12 public User getUserInfoById(Integer id) { 13 return userDao.findById(id); 14 } 15 16 @DynamicSwitchDataSource(dataSource = "datasource0") 17 public void save(User user) { 18 userDao.save(user); 19 } 20 21 @DynamicSwitchDataSource(dataSource = "datasource1") 22 public List<User> findAll(){ 23 String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u"; 24 List<User> list =commonDao.findListBySQL(sql,User.class); 25 return list; 26 } 27 28 }
也可以不适用AOP,直接在编程中实现,通过测试,结果分别为两个数据库中的信息
1 public void test(){ 2 HandlerDataSource.putDataSource("datasource1"); 3 String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u"; 4 List<User> list =commonDao.findListBySQL(sql,User.class); 5 6 HandlerDataSource.putDataSource("datasource0"); 7 commonDao.deleteById("2",User.class); 8 }
实现原理,MultipleDataSourceToChoose的继承结构图,之前说过他是DataSource的子类,由于无论我们是使用Mybatis还是使用Hibernate进行SQL操作的时候总会执行getConnection(),无论我们的数据源是否使用了数据库连接池,因为数据库连接池的主要作用就是保持一堆的Connection不进行关闭的处理,节省我们的关闭和打开连接的开销。http://blog.csdn.net/shuaihj/article/details/14223015/ 浅谈数据库连接池说的简单易懂。 Connection getConnection() throws SQLException;所以这句话总是要执行的,只是AbstractRoutingDataSource这个类给我们进行了一些中介的处理,在获取Connection的时候会去寻找保存的DataSource的引用,到底是选择哪个DataSource进行处理,看代码!
配置的参数
1 <bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true"> 2 <description>数据源</description> 3 <property name="targetDataSources"> 4 <map key-type="java.lang.String" value-type="javax.sql.DataSource"> 5 <entry key="datasource0" value-ref="dataSource0" /> 6 <entry key="datasource1" value-ref="dataSource1" /> 7 </map> 8 </property> 9 <!-- 设置默认的目标数据源 --> 10 <property name="defaultTargetDataSource" ref="dataSource0" /> 11 </bean>
targetDataSources,是一个Map对于数据源的引用
1 public void setTargetDataSources(Map<Object, Object> targetDataSources) { 2 this.targetDataSources = targetDataSources; 3 }
对于实现SQL的Connection getConnection() throws SQLException的实现,其实就是代理模式找到之前Map的引用,通过key,而这个key就是我们灵活配置的key,通过这个key就可以寻找到这个值。
1 public Connection getConnection() throws SQLException { 2 return determineTargetDataSource().getConnection(); 3 }
这里说的非常的详细,通过钩子函数让子类去实现,寻找特定的key,然后选择DataSource 的时候就可以很灵活的使用啦!
1 /** 2 * Retrieve the current target DataSource. Determines the 3 * {@link #determineCurrentLookupKey() current lookup key}, performs 4 * a lookup in the {@link #setTargetDataSources targetDataSources} map, 5 * falls back to the specified 6 * {@link #setDefaultTargetDataSource default target DataSource} if necessary. 7 * @see #determineCurrentLookupKey() 8 */ 9 protected DataSource determineTargetDataSource() { 10 Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); 11 Object lookupKey = determineCurrentLookupKey(); 12 DataSource dataSource = this.resolvedDataSources.get(lookupKey); 13 if (dataSource == null && (this.lenientFallback || lookupKey == null)) { 14 dataSource = this.resolvedDefaultDataSource; 15 } 16 if (dataSource == null) { 17 throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); 18 } 19 return dataSource; 20 }
这个就是模板方法模式中常见的钩子函数,在HttpServlet中也有类似的使用钩子,非常的棒,不过这个是必须实现,httpServlet不是必须实现,只是添加一些补充。由于每次执行数据库的调用,总会执行这个getConnection方法,每次都查看AOP中是否设置了当前的数据源,然后找到Map的引用的代理的数据源的Connection方法,原理没有变化的。
1 /** 2 * Determine the current lookup key. This will typically be 3 * implemented to check a thread-bound transaction context. 4 * <p>Allows for arbitrary keys. The returned key needs 5 * to match the stored lookup key type, as resolved by the 6 * {@link #resolveSpecifiedLookupKey} method. 7 */ 8 protected abstract Object determineCurrentLookupKey();
这里就是我们的实现的数据源的选择哦!
1 /** 2 * descrption: 多数据源的选择 3 * authohr: wangji 4 * date: 2017-08-21 10:32 5 */ 6 public class MultipleDataSourceToChoose extends AbstractRoutingDataSource { 7 8 /** 9 * @desction: 根据Key获取数据源的信息,上层抽象函数的钩子 10 * @author: wangji 11 * @date: 2017/8/21 12 * @param: 13 * @return: 14 */ 15 @Override 16 protected Object determineCurrentLookupKey() { 17 return HandlerDataSource.getDataSource(); 18 } 19 }
版权声明:本文为CSDN博主「汪小哥」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012881904/article/details/77449710