博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
事务范围数据库读写分离失败
阅读量:5322 次
发布时间:2019-06-14

本文共 3768 字,大约阅读时间需要 12 分钟。

  • 背景:

xxx系统在账单生成环节和对账环节采用了spring线程池技术。

系统在第一次执行账单生成可以顺利通过,但是当第二次再执行生成账单时,报出没有数据库写入权限。
事务范围内,主从数据源切换失效,只能获取到主库数据源。

  • 配置:

为了方便说明问题,配置线程池默认活动线程1个,最大线程1个。如下:

  • 问题分析:

首先分析为什么事务范围只能拿到主库的数据源。目前事务的声明采用注解的方式,如下:

@Transactional(rollbackFor=Exception.class)
public void genBillB(){}
然后我们分析下程序的执行流程:
1、spring解析Transactional并开启事务
通过阅读spring源码,我们发现事务真正开始的地方为:
org.springframework.jdbc.datasource.DataSourceTransactionManager类的方法:doBegin(Object, TransactionDefinition).在该方法中会获取当前数据源对应的connection并绑定到
当前事务中。代码如下:

DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {if (txObject.getConnectionHolder() == null ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {Connection newCon = this.dataSource.getConnection();if (logger.isDebugEnabled()) {logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,// so we don't want to do it unnecessarily (for example if we've explicitly// configured the connection pool to set it already).if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}txObject.getConnectionHolder().setTransactionActive(true);int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// Bind the session holder to the thread.if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());}}catch (Exception ex) {DataSourceUtils.releaseConnection(con, this.dataSource);throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}

 

大家可能会问,当前数据源是哪个?请大家回忆上一篇博文的序列图,如果当前事务中没有绑定数据源或者没有开启事务的时候,会直接调用

AbstractRoutingDataSource.getConnection。该方法调用DynamicDataSource.determineCurrentLookupKey()从ThreadLodal变量DynamicDataSource.local中获取
当前数据源路由key,根据key获取当前的数据源,让后调用当前数据源的getConnection方法得到数据库连接。
那么当第一次调用时很明显DynamicDataSource.local中为空,参看DynamicDataSource中的方法determineCurrentLookupKey,此时返回的一定是master数据源。也就是说当前数据源为master数据源。
@Override
protected Object determineCurrentLookupKey() {
String dString = local.get() == null ? MASTER : local.get();
setRoute(DynamicDataSource.MASTER);
return dString;
}
获取到当前数据源,参看上面源码,spring会设置txObject.getConnectionHolder().setSynchronizedWithTransaction(true);同时将数据源绑定到当前事务
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
到此事务就开启完毕了。
2、事务开启后,开始进入具体的业务逻辑代码
在业务逻辑代码中,有查询和更新。查询我们期望每次使用的是从库,更新期望每次都是主库。但是结合上一篇博文分析,如果在事务范围内,每次的数据库连接是通过
调用ConnectionHolder.getConnection得到,而该ConnectionHolder在第一步的时候也说明已经绑定到当前事务并且数据源为master。所以即便是我们显示的声明要获取从库连接也不会生效。
代码如下:this.getJdbcTemplate(DynamicDataSource.SLAVE).query()也会使用master数据源而不会使用slave。

下面分析线程池中,系统在第一次执行账单生成可以顺利通过,但是当第二次再执行生成账单时,报出没有数据库写入权限。
1、什么是线程池
维护一定数量的线程,减少在创建和销毁线程上所花的时间以及系统资源的开销。
2、第二次执行
了解了线程池的概念后我们知道,第二次执行获取到的是上一次执行创建的线程。由于上一个线程的ThreadLocal变量,在程序执行的最后环境被设置为slave,
因此其存放的是slave数据源,那么根据第一个问题分析,事务在开启的时候就会从threadlocal变量中找到当前的数据源并绑定,由于当前线程的theadlocal变量中key为slave
那么获取到的数据源为slave,这样当进行写入操作时就会提示拒绝操作。

  • 结论:

应该尽量将查询放到事务外部处理

线程池中使用时,保证线程执行完毕后清理threadlocal变量。

转载于:https://www.cnblogs.com/belen/p/4926206.html

你可能感兴趣的文章
套接口和I/O通信
查看>>
阿里巴巴面试之利用两个int值实现读写锁
查看>>
浅谈性能测试
查看>>
Winform 菜单和工具栏控件
查看>>
jequery动态创建form
查看>>
CDH版本大数据集群下搭建的Hue详细启动步骤(图文详解)
查看>>
第六次java作业
查看>>
巧用Win+R
查看>>
浅析原生js模仿addclass和removeclass
查看>>
Python中的greenlet包实现并发编程的入门教程
查看>>
java中遍历属性字段及值(常见方法)
查看>>
Jenkins执行批处理文件失败
查看>>
深入理解jQuery框架-框架结构
查看>>
[7.14NOIP模拟4]通讯 题解 (Tarjan缩点+贪心)
查看>>
YUI3自动加载树实现
查看>>
python知识思维导图
查看>>
当心JavaScript奇葩的逗号表达式
查看>>
App Store最新审核指南(2015年3月更新版)
查看>>
织梦MIP文章内容页图片适配百度MIP规范
查看>>
点击复制插件clipboard.js
查看>>