现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用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. 敏捷遇上UML—软创基地马年大会(广州站 2014-4-19)

        我们将在广州为您奉献高端知识大餐,当敏捷遇上UML,会发生怎样的化学作用呢?首席专家张老师将会为您分享需求分析及软件设计方面的最佳实践,帮助您掌握敏捷.UML及两者相结合的实战技巧. 时间:2 ...

  2. 具备 jQuery 经验的人如何学习AngularJS(附:学习路径)

    这是一个来自stackoverflow的问答,三哥直接把最佳回答搬过来了. 都说AngularJS的学习曲线异常诡异~~~ Q: “Thinking in AngularJS” if I have a ...

  3. JavaScript Array和string的转换

    Array类可以如下定义: var aValues = new Array(); 如果预先知道数组的长度,可以用参数传递长度 var aValues = new Array(20); -------- ...

  4. fillStyle图片填充

    图片自找 <!DOCTYPE HTML> <head> <meta charset = "utf-8"> <title>canvas ...

  5. StringUtils工具类

    StringUtils源码,使用的是commons-lang3-3.1包.下载地址 http://commons.apache.org/lang/download_lang.cgi 以下是String ...

  6. 关于xml的使用。

    使用的常用类: XmlSerializer ParaMapping StreamReader DirectionaryInfo FileInfo using as object 例子: public ...

  7. 《java JDK7 学习笔记》之异常处理

    1.java中所有的错误都会被打包为对象,JVM会尝试执行try区块中的程序代码,如果发生错误,执行流程会跳离错误发生点,然后比较catch括号中声明的异常类型,是否符合被抛出的错误对象类型,如果是的 ...

  8. Android 分享到微信

    /** * 分享信息到朋友 * * @param file,假如图片的路径为path,那么file = new File(path); */ private void shareToFriend(Fi ...

  9. Python实用环境pyenv搭建教程

    实验系统:kubuntu-15.10-desktop-amd64 关于pyenv的介绍:一般在操作系统中我们会安装多个Python版本,在*nix系统中一般默认就自带了Python2与Python3两 ...

  10. WPF 3D 知识点大全以及实例

    引言 现在物联网概念这么火,如果监控的信息能够实时在手机的客服端中以3D形式展示给我们,那种体验大家可以发挥自己的想象. 那生活中我们还有很多地方用到这些,如上图所示的Kinect 在医疗上的应用,当 ...