现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用Spring AOP来达到切换数据源,查询不同的数据库就可以了。

  如果以前的我,可能就1个数据库->1个数据源->1个SessionFactory->1个事务管理,按照这样的逻辑,操作一个数据库是没什么问题的,但是两个甚至多个这样的相同配置,这不是要逼死强迫症患者的节奏吗?

  Spring动态切换数据库的原理是通过继承AbstractRoutingDataSource重写determineCurrentLookupKey()方法,来决定使用那个数据库。在开启事务之前,通过改变lookupKey来达到切换数据源目的。

  先写DataSourceHolder用来保存当前线程的数据库源。

public class DataSourceHolder {

    private static final ThreadLocal<String> datasourcce = new ThreadLocal<String>();

    public static void setCustomeType(String type){
datasourcce.set(type);
} public static String getCustomeType(){
return datasourcce.get();
} public static void remove(){
datasourcce.remove();
}
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getCustomeType();
} }

  ThreadLocal用作保存数据库源的key就可以了,相应的数据库源会在切换的时候从AbstractRoutingDataSource的Map<Object, Object> targetDataSources中获取。

<bean name="db1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.db1.url}" />
<property name="username" value="${jdbc.db1.username}" />
<property name="password" value="${jdbc.db1.password}" />
</bean> <bean name="db2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.db2.url}" />
<property name="username" value="${jdbc.db2.username}" />
<property name="password" value="${jdbc.db2.password}" />
</bean> <bean id="dataSource" class="com.test.dynamic.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="db1" value-ref="db1" />
<entry key="db2" value-ref="db2" />
</map>
</property>
<property name="defaultTargetDataSource" ref="db1" />
</bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan">
<list>
<value>${packagesToScan}</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
</props>
</property>
</bean> <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean> <aop:config>
<aop:pointcut expression="${aop.expression}" id="bussinessService"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="bussinessService" order="2"/>

      <aop:aspect ref="dataSourceAspect" order="1">
        <aop:before method="changeDateSource" pointcut="@annotation(com.test.annotation.DataSource)"/>
      </aop:aspect>

    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>

  这次使用的是@annotation的方式的AOP切面,当然也可以使用基于正则的AOP切面,接下来写DataSourceAspect。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
public String name() default "";
}
import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component; import com.test.annotation.DataSource; @Component
public class DataSourceAspect { public void changeDateSource(JoinPoint jp){
try{
String methodName = jp.getSignature().getName();
Class<?> targetClass = Class.forName(jp.getTarget().getClass().getName());
for(Method method : targetClass.getMethods()){
if(methodName.equals(method.getName())){
Class<?>[] args = method.getParameterTypes();
if(args.length == jp.getArgs().length){
DataSource ds = method.getAnnotation(DataSource.class);
DataSourceHolder.setCustomeType(ds.name());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} }

  这个使用的时候很简单,只要在需要切换数据源上的方法加一个注解@DataSource(name="db1"),就可以了。由于我们做事务控制的在Service层,所以在Dao层上切换是不行的。只能在Controller层和Service做切换,而且在Service切换需要在切面上加order属性,order属性越小,就越先执行,只要切换的逻辑在开始事务前执行就可以了。

  1、那么问题来了,可以在Service同一个方法上访问两个不同的数据库吗?

  不可以的。但是可以在Controller访问Service的两个不同方法。

  2、不同的数据库方言要换吗?

  其实是不用换的,方言不配置也可以(其实还没试过,理论上-.-),经试验,方言默认为默认数据源的方言,由mysql切换为oracle需要注意。

  3、要注意什么?

  注意hibernate扫面默认的数据源就好了,hibernate.hbm2ddl.auto设置为validate,数据库表手动建。  

  

Spring AOP动态切换数据源的更多相关文章

  1. Spring+Mybatis动态切换数据源

    功能需求是公司要做一个大的运营平台: 1.运营平台有自身的数据库,维护用户.角色.菜单.部分以及权限等基本功能. 2.运营平台还需要提供其他不同服务(服务A,服务B)的后台运营,服务A.服务B的数据库 ...

  2. Spring MVC动态切换数据源(多数据库类型)

    最近由于项目需求,需要将Sql Server 和 Mysql 两种数据库整合到一个项目,项目的用到的框架是SSM. 因此尝试了利用AOP切面来切每次执行的Servcie方法,根据Service所在的包 ...

  3. 实战:Spring AOP实现多数据源动态切换

    需求背景 去年底,公司项目有一个需求中有个接口需要用到平台.算法.大数据等三个不同数据库的数据进行计算.组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分. 直到今年回来了,这个 ...

  4. Spring学习总结(16)——Spring AOP实现执行数据库操作前根据业务来动态切换数据源

    深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写 ...

  5. Spring + Mybatis 项目实现动态切换数据源

    项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库. 最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩展很有限,所有采用下面的办法. ...

  6. Spring动态切换数据源及事务

    前段时间花了几天来解决公司框架ssm上事务问题.如果不动态切换数据源话,直接使用spring的事务配置,是完全没有问题的.由于框架用于各个项目的快速搭建,少去配置各个数据源配置xml文件等.采用了动态 ...

  7. AOP获取方法注解实现动态切换数据源

    AOP获取方法注解实现动态切换数据源(以下方式尚未经过测试,仅提供思路) ------ 自定义一个用于切换数据源的注解: package com.xxx.annotation; import org. ...

  8. Spring Boot 如何动态切换数据源

    本章是一个完整的 Spring Boot 动态数据源切换示例,例如主数据库使用 lionsea 从数据库 lionsea_slave1.lionsea_slave2.只需要在对应的代码上使用 Data ...

  9. 在使用 Spring Boot 和 MyBatis 动态切换数据源时遇到的问题以及解决方法

    相关项目地址:https://github.com/helloworlde/SpringBoot-DynamicDataSource 1. org.apache.ibatis.binding.Bind ...

随机推荐

  1. String.Empty、null、“” 区别

    概念准备: 1.引用类型是将对象是实际数据保存在堆中, 将对象在堆中的地址保存在栈中. 2.值类型直接将实际数据存放在堆中,不会将对象在堆中的地址保存在栈中. 一.String.Empty和" ...

  2. Linux iptables配置错误导致ORA-12535 & ORA-12170

    实验环境:       操作系统  : Red Hat Enterprise Linux Server release 5.7 (Tikanga)       数据库版本: Oracle Databa ...

  3. Spring配置JNDI和通过JNDI获取DataSource

    一.SpringJNDI数据源配置信息 <bean id="dataSource" class="org.springframework.jndi.JndiObje ...

  4. linux c++应用程序内存高或者占用CPU高的解决方案_20161213

    对于绝大多数实时程序来说,实时处理相关程序中的循环问题所带来的对机器的损耗和自身的处理速度的平衡,以及与其他程序的交互以及对其他功能的影响难免会成为程序设计中最大的障碍同时也是最大的突破点. 在所有这 ...

  5. AD 域账号登录

    域服务数据读写,有俩种模式 1.轻量级的数据读取 _domain是服务器的域名 获取连接PrincipalContext pc = new PrincipalContext(ContextType.D ...

  6. jni调试3(线程调试env变量问题)

    jni层调试线程死机原因 一,导致死机原因:   jni层中  线程函数中  只要添加调用env 的函数 ,,就会死机     二,解决方法 第一我们应该理解: ①(独立性) JNIEnv 是一个与线 ...

  7. linux命令之tail

    tail用于输出文件末尾部分.一个比较有用的功能是tail + grep实现类似于安卓开发时调试使用的logcat,具体操作是: 一般我是用SecureCRT连接linux,然后使用SecureCRT ...

  8. 推荐一些python Beautiful Soup学习网址

    前言:这几天忙着写分析报告,实在没精力去研究django,虽然抽时间去看了几遍中文文档,还是等实际实践后写几篇操作文章吧! 正文:以下是本人前段时间学习bs4库找的一些网址,在学习的可以参考下,有点多 ...

  9. java异常面试题

    1.try{}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后? 也许你的答案是在return之前,但往更细地说, ...

  10. 洛谷CON1041 NOIP模拟赛一试

    A T2-power of 2 题目描述 是一个十分特殊的式子. 例如: n=0时 =2 然而,太大了 所以,我们让对10007 取模 输入输出格式 输入格式: n 输出格式:  % 10007 输入 ...