前段时间花了几天来解决公司框架ssm上事务问题。如果不动态切换数据源话,直接使用spring的事务配置,是完全没有问题的。由于框架用于各个项目的快速搭建,少去配置各个数据源配置xml文件等。采用了动态切换数据源方式。在解决问题的时候查看了相关源代码等。接下来对动态数据源切换、事务相关的核心源代码个分析总结,总结不到位,请谅解。

第一、实现动态切换数据源

思路大概如下:具体切换到哪个数据源通过包名来控制,写一个类实现使用spring提供MethodInterceper接口,再通过aop来切面到service,这样来确定我们需要的数据源。

我们通过在properties文件中配置相关数据连接配置:如:

在公用工程中只需简单配置sys系统库的数据源配置,因为任何工程都需要这个基础的数据源,如下图:

在properties文件中,我们配置了3个数据库的连接,其中sys系统库已经配置了,那么其他两个库数据源没有在xml中配置,我们可以通过启动项目后由spring给我们自动创建相关的bean,spring的强大之处哦。如下面这段代码是创建相关数据源bean的代码:

/**
* <Description>
* 动态生成其他数据源bean,并且注册到spring容器中。
*
* 实现spring ApplicationContextAware接口,spring在实例化此bean时候会自动调用setApplicationContext方法,
* 这样此bean就具有拿到容器,那么你想怎么搞就怎么搞了。
*
* 实现InitializingBean接口,我们知道在初始化bean的时候,会自动调用afterPropertiesSet方法,在spring源码中有大量
* 实现了此接口的类。可以看出spring的为我们提供了强大的扩展性
* @author 兰伟
* @CreateDate 2018年6月9日 上午12:07:36
* @since V1.0
*/
public class DataSources implements ApplicationContextAware, InitializingBean {
private static Logger logger = Logger.getLogger(DataSources.class);
public static String DEFAULT_DATASOURCE="ds.sys";
private ApplicationContext context; @Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
} @Override
public void afterPropertiesSet() {
try {
regDynamicBean();
}
catch (Exception e) {
e.printStackTrace();
}
} private void regDynamicBean() throws IOException{
//CustomPropertyConfigurer中读取了properties配置文件
Map<String, Object> pmap = CustomPropertyConfigurer.getctxPropertiesMap();
Map<String, DataSourceInfo> mapCustom = getDbSourceInfo(pmap);
// 把数据源bean注册到容器中
addSourceBeanToApp(mapCustom); }
@SuppressWarnings("unchecked")
private void addSourceBeanToApp(Map<String, DataSourceInfo> mapCustom) {
DefaultListableBeanFactory acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
BeanDefinition beanDefinition;
Iterator<String> iter = mapCustom.keySet().iterator();
while(iter.hasNext()){
String dataSourceId = iter.next();
// 得到Bean定义,并添加到容器中
beanDefinition = new ChildBeanDefinition(DEFAULT_DATASOURCE);
// 注意:必须先注册到容器中,再得到Bean进行修改,否则数据源属性不能有效修改
acf.registerBeanDefinition(dataSourceId, beanDefinition);
// 再得到数据源Bean定义,并修改连接相关的属性
DruidDataSource cpds = (DruidDataSource)context.getBean( dataSourceId);
cpds.setUrl(mapCustom.get(dataSourceId).connUrl);
cpds.setUsername(mapCustom.get(dataSourceId).userName);
cpds.setPassword(mapCustom.get(dataSourceId).password);
cpds.setDriverClassName(mapCustom.get(dataSourceId).driverClass);
if(cpds.getDriverClassName().indexOf( "jtds" )!=-1||cpds.getDriverClassName().indexOf( "sqlserver" )!=-1){
cpds.setValidationQuery("select 1");
}else if( cpds.getDriverClassName().indexOf( "oracle" )!=-1 ){
cpds.setValidationQuery("SELECT 'x' FROM dual");
}
((Map<String, Object>) SpringUtils.getBean("targetDataSources")).put(dataSourceId, cpds);
}
}
public Map<String, DataSourceInfo> getDbSourceInfo(Map<String, Object> pmap,boolean includeSys) throws IOException {
Map<String, DataSourceInfo> mapDataSource = new HashMap<String,DataSourceInfo>();
String source = (String) pmap.get("db.source");
if(source==null || source.equals("jndi")){
return mapDataSource;
}
Matcher matcher;
Pattern pattern = Pattern.compile("^(\\w+\\.\\w+)\\.jdbc\\.(url|username|password|driverclass|validationQuery)$");
for(Map.Entry<String, Object> entry : pmap.entrySet()) {
String keyProp = entry.getKey();
String valueProp = entry.getValue().toString();
matcher = pattern.matcher(keyProp );
if(matcher.find()){
String dsName = matcher.group(1);
if(StringUtils.equals(dsName, DEFAULT_DATASOURCE) && !includeSys){
continue;
}
String dsPropName = matcher.group(2);
DataSourceInfo dsi;
if(mapDataSource.containsKey(dsName)){
dsi = mapDataSource.get(dsName);
}
else{
dsi = new DataSourceInfo();
}
// 根据属性名给数据源属性赋值
if("url".equals(dsPropName)){
dsi.connUrl = valueProp;
}else if("username".equals(dsPropName)){
dsi.userName = valueProp;
}else if("password".equals(dsPropName)){
dsi.password = valueProp;
}else if("driverclass".equals(dsPropName)){
dsi.driverClass = valueProp;
}else if("validationQuery".equals(dsPropName)){
dsi.validationQuery = valueProp;
}
mapDataSource.put(dsName, dsi);
}
}
return mapDataSource;
}
public Map<String, DataSourceInfo> getDbSourceInfo(Map<String, Object> pmap) throws IOException {
return getDbSourceInfo(pmap,false);
} public class DataSourceInfo{
public String connUrl;
public String userName;
public String password;
public String driverClass;
public String validationQuery;
}
}

  

在上面我们已经实现了各个数据源。接下来就是根据包名来动态切换数据源的问题了。如何切换,我们最好不要在业务代码中来写什么DataSourceContextHolder.setDbType(”ds.sms”)这样的代码。我们可以通过spring提供MethodIntercepter接口,采用aop的方式来。

及spring的相关配置

Spring中提供AbstractRoutingDataSource抽象类,重写determineCurrentLookupKey方法,当需要查询数据的时候会自动切换到指定数据库,核心代码如下

DataSourceContextHolder核心代码,在这里有个局部线程变量,这是和线程绑定起来。我们在写代码的时候一定要注意,不要随意开启新线程,否则是不起作用的哦,除非重新在设置一把。

动态切换数据源主要工作已完成.

第二、接下来看看事务以及mybatis与spring整合核心配置

在配置中我们可以看到自己去实现了一套DynamicSqlSessionTemplate、DynamicSqlSessionFactoryBean、CustomDataSourceTransactionManager。这几个是修改后的。在之前的代码中,我们还是采用自带的SqlSessionTemplate、SqlSessionFactoryBean、DataSourceTransactionManager,如果使用之前的,在看下datasource的配置,这个是系统库,那么切换数据源后,两个DataSource就不一致的。这就会导致同一个线程中始终是无法获取到由spring管理的事务相关设置。所以想要保证事务的话,必须要datasource是同一个。所以自己就实现了一个DataSourceTransactionManager,修改了getDataSource方法等。

Mybatis与spring整合事务相关的核心源码可以查看

TransactionInterceptor事务拦截器,继承了TransactionAspectSupport类

TransactionAspectSupport

AbstractPlatformTransactionManager

SqlSessionUtils  有个核心方法getSqlSession,这个就需要从当前线程中去获取sqlSesson

DataSourceTransactionManager 事务管理 继承了AbstractPlatformTransactionManager。

SqlSessionFactoryBean

SqlSessionTemplate这里面有个核心SqlSessionInterceptor拦截器,其实也是个代理通过代理模式来。

这么几个核心类。

有篇博客转门对DataSourceTransactionManager 核心的几个类做了分析

做了源码分析https://www.cnblogs.com/chihirotan/p/6739748.html,大家可以参考下。

在解决问题之前没有找到他的这篇文章,害的我挨着撸了一把spring及mybatis-spring的这块的源码。如果早看到的话,估计时间会少花点。

Spring动态切换数据源及事务的更多相关文章

  1. spring动态切换数据源(一)

    介绍下spring数据源连接的源码类:| 1 spring动态切换连接池需要类AbstractRoutingDataSource的源码 2 /* 3 * Copyright 2002-2017 the ...

  2. Spring动态切换数据源

    11 //定义数据源枚举public enum DataSourceKey { master, slave, } 22 /** * 数据源路由 */ @Slf4j public class Dynam ...

  3. Spring动态切换多数据源事务开启后,动态数据源切换失效解决方案

    关于某操作中开启事务后,动态切换数据源机制失效的问题,暂时想到一个取巧的方法,在Spring声明式事务配置中,可对不改变数据库数据的方法采用不支持事务的配置,如下: 对单纯查询数据的操作设置为不支持事 ...

  4. Spring AOP动态切换数据源

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. git 公钥的使用

    码云 https://gitee.com/ ,之前在教程视频中看到使用 码云  今天自己也撸了一把.第一次使用.打开官方网站看到免费开通企业版,就点了这个原本以为需要填写很多资料,实际操作下来,就一个 ...

  2. fastclick插件学习(一)之用法

    原理 在检测到touchend事件后, 会通过dom自定义事件模拟一个click事件,并把浏览器300ms之后真正触发的点击事件屏蔽掉,fastclick是不会对PC浏览器添加监听事件 使用 1.引入 ...

  3. arcgis js之点击获取featureLayer中的点

    arcgis js之点击获取featureLayer中的点 代码: this.view.on('click', (evt) => { let layer = this.map.findLayer ...

  4. JS ES6

    变量 let 块级作用域内有效 不能重复声明 不会预处理,不存在提升 var btns = document.getElementsByTagName('button'); for (let i = ...

  5. 常见python面试题-手写代码系列

    1.如何反向迭代一个序列 #如果是一个list,最快的方法使用reversetempList = [1,2,3,4]tempList.reverse()for x in tempList:    pr ...

  6. Image Processing and Analysis_8_Edge Detection:Multiresolution edge detection techniques ——1995

    此主要讨论图像处理与分析.虽然计算机视觉部分的有些内容比如特 征提取等也可以归结到图像分析中来,但鉴于它们与计算机视觉的紧密联系,以 及它们的出处,没有把它们纳入到图像处理与分析中来.同样,这里面也有 ...

  7. 认识和学习bash

    认识Bash这个Shell 查看linux下shells: [shichaogeng@study etc]$ vim /etc/shells 查看登入时取得到的shell: [shichaogeng@ ...

  8. Struts2之jsp页面取得当前actionName

    在页面上加入<s:debug />, 我们就可以查看stackContext的信息 其中有一项:Key为com.opensymphony.xwork2.ActionContext.name ...

  9. 我的第一个Node.js项目

    Node.js的安装通常有两种方式:自己编译源代码和使用编译好的文件,我这里使用编译好的文件目前我的home目录下有刚下载来的node-v4.2.3-linux-x641.首先解压缩 tar xvf ...

  10. JSP常用标签

    JSP常用标签可以理解为JSTL user:普通用户 admin:站点管理员 JSTL1.1.2下载地址:http://archive.apache.org/dist/jakarta/taglibs/ ...