Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题
Spring是目前Java开发中最流行的框架了,它的事务管理我们在开发中常常的用到,但是很多人不理解它事务的原理,导致开发中遇到事务方面的问题往往都要用很长的时间才能解决,下面就带着大家去深入了解Spring的事务,然后文章的最后还会给出开发中常常遇到的问题以及解决方案。
如果单纯的用Spring框架进行开发(PS使用注解开发,不是XML配置的方式)。那么要使用Spring事物我们首先要加的就是Spring的这个【EnableTransactionManagement】注解(PS如果直接使用了Spingboot框架了,它已经使用自动配置相关的原理自动加了这个注解了)。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
上面注解的主要作用就是帮我们Import了TransactionManagementConfigurationSelector这个类,它的主要功能如下:
这个类目前就是帮我们在Spring中注入了ProxyTransactionManagementConfiguration,大家一看名称就知道这是一个配置类(配置类一般都是帮我们注入各种需要的Bean)如下它帮我们做的事情。
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
return advisor;
} @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
} @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
return interceptor;
}
上面的代码可以看出它帮我们做了三件事,1:导入了一个增强器。2:导入了一个事务属性的资源。3:导入一个事务的拦截器。这三个类后面讲事务的时候都会用到。
如果方法上加了事物注解的类,Spring会创建它的一个代理类放到容器中,如果没加注解(PS本文只考虑事物注解不考虑其他的)那么Spring就会把它原始的对象放到容器中这个很重要,后面总结事物为什么会失效。(下面第一张图没加Transaction注解的对象,第二张图是加了的,然后Sping就把它转换为代理对象,具体怎么转换的是AOP相关功能,AOP很多功能点本文不做具体讲解)【上面说的一段话很是重要】。
重点来了,下面是我UserService中的主要的方法,很简单如下:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao ;
@Autowired
private ApplicationContext applicationContext ;
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void addUser(User user) {
user.setPersonName("xiaozhang");
userDao.insert(user) ;
user.setPersonName("xiaozhang02");
UserService userService = applicationContext.getBean(UserService.class);
userService.addUser02(user);
int i= 1/0 ; //让程序抛出异常 }
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void addUser02(User user02) {
userDao.insert(user02) ;
}
}
然后就开始带着大家看相关的源码,首先打断点会进入到如下的方法(你如果想尝试就在这个类【TransactionAspectSupport】的如下方法中打一个断点)。
分析上面代码主要功能。
--1:这段代码是获取事务的属性,上面配置中配置了
final TransactionAttribute txAttr = (tas != null ?
tas.getTransactionAttribute(method, targetClass) : null);
--2:决定使用那个事务管理器
final PlatformTransactionManager tm =
determineTransactionManager(txAttr);
--3:找方法的切入点也就是加了事务注解的方法
final String joinpointIdentification =
methodIdentification(method, targetClass, txAttr);
上面图中的三个方法就是注释中写的作用,后面就是事务的重点了,然后会走下面的方法。
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr); //这个重点方法
}
tm.getTransaction(txAttr),这个方法会调用如下方法:
//AbstractPlatformTransactionManager这个类的方法
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
//获取事务管理器,第一次获取的为空
Object transaction = doGetTransaction();
//看看是否存在事务,第一次不存在。第二次会进入到这个方法,后面会重点讲。
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
//我们addUser的方法事务传播行为是PROPAGATION_REQUIRED,所以走这个分支
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
//挂起事务,后面PROPAGATION_REQUIRES_NEW这个方法很重要
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
//创建一个事务状态
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//重点来了,开启事务
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
开启事务方法(doBegin)(这个方法做了如下6件很重要的事情)
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
//1:获取事务连接,我们写JDBC的时候也是需要自己获取事务连接的
Connection con = null;
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
//2:设置数据库的隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
//3:关闭数据库的自动提交
// 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);
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
// 4:激活事务
txObject.getConnectionHolder().setTransactionActive(true);
// 5:设置超时的时间
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
//6:绑定数据源和连接持有器。
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
上面方法完成后,开始执行下面这个方法:
try {
这个方法的主要意思是去执行目标方法,也就是(addUser)
// This is an around advice:
//Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
去执行目标方法(addUser)后,就又回到前面那个第一次进入Spring源码中的方法(因为从上面的代码可知我们的addUser方法又调用了其他事务方法)如下图:
注意此时的addUser()方法还没执行完,又进入同样的方法如下:
上面很多重复的就不讲了,前面由于已经存在了事务所以会进入如下方法。
//前面说要重点说的
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
//不允许有事务就抛出异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
//重点也是我们开发中常用的事务传播行为(新建一个事务)
// PROPAGATION_REQUIRES_NEW
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
//先把旧的事务挂起,并且缓存起来,
// 怎么缓存旧的事务的自己可走进去看一下,因为前面事务还没提交,还需要用
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//又开启新的事务了,和上面讲过的同样的套路了
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
后面就和前面一样了流程。最后数据库结果如下(PS忽略我的数据库表设计,只是为了简单,用的Mysql数据库也是为了自己电脑方便安装)。
结果addUser插入失败了,addUser02插入成功了。(这也就是事务传播属性,Propagation.REQUIRED和Propagation.REQUIRES_NEW的区别)。
好了我们进行总结在工作中事务方面的问题。
1:事务注解使用不当,造成事务不生效(PS只举列开发中常用的)。
1.1 :没有用代理对象调用,直接用原始对象(文章开始说了加了事务注解的会生成一个代理对象),如下的使用事务会失效(也是大多数开发者常犯的错误)。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao ;
@Autowired
private ApplicationContext applicationContext ;
@Override
public void addUser(User user) {
user.setPersonName("xiaozhang");
userDao.insert(user) ;
user.setPersonName("xiaozhang03");
//用this没有AOP代理对象,造成事务失效,2个user都会插入数据库
this.addUser02(user) ;
}
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void addUser02(User user02) {
userDao.insert(user02) ;
int i= 1/0 ; //让程序抛出异常
}
}
1.2 rollbackFor = Exception.class如果不加,Spring只回滚运行时候的异常和程序中的Error(这是很细节的代码) 。
上面2个问题解决方案大家看完也就很明白怎样解决了。
2:事务嵌套使用不当导致事务都回滚了,如下有时候会写下面的业务。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao ;
@Autowired
private ApplicationContext applicationContext ;
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void addUser(User user) {
user.setPersonName("xiaozhang");
userDao.insert(user) ;
user.setPersonName("xiaozhang02");
UserServiceImpl userService = (UserServiceImpl)applicationContext.getBean(UserService.class);
try {
userService.addUser02(user);
}catch (Exception e){
System.out.println(e);
}
}
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void addUser02(User user02) {
userDao.insert(user02) ;
int i= 1/0 ; //让程序抛出异常
}
}
很多时候我们一看代码也没什么问题addUser()这个方法会新增数据的,因为把addUser02()方法抛出的异常捕获了。开发的时候(PS开发的时候调用的其他服务的业务方法,我为了演示简单这样写了)我遇到这方面的问题找了很久才找到原因。会抛出如下异常:
然后我们拿Spring源码分析,上面写的两个事务传播属性都是propagation = Propagation.REQUIRED (PS如果存在事务就使用原来的事务,不存在就新启一个事务)。
简单的说一下为什么会出现上面的原因,addUser02()这个方法抛出异常了它会把一个全局的rollback-only变量标记为true(默认为false)。由于它和addUser()方法共用事务,所以后面这个方法事务提交的时候会检查这个变量,如果看到这个变量为true。就把它自己也回滚了。如下:
下面addUser()方法要进行提交事务了如下:
然后会进入AbstractPlatformTransactionManager这个类的commit方法如下图。
然后会进入processRollback这个方法,为什么会进入这个方法还是因为rollback-only这个变量上一步设置为true了。(图片中可以看到是那个类的这个方法,自己Debug打断点的时候可参考),这个就是我们前面图中抛出的异常(Transaction rolled back because it has been marked as rollback-only)。
private void processRollback(DefaultTransactionStatus status, boolean unexpected){
try {
//unexpected 为true 。
boolean unexpectedRollback = unexpected;
// Unexpected rollback only matters here if we're asked to fail early
//这个方法不会进入
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
// unexpectedRollback 为true 就会抛出开始说的那个异常。
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
遇到上面的这个问题怎么解决的?
1:addUser02()这个方法新建一个事物也就是使用事物传播属性propagation = Propagation.REQUIRES_NEW(PS如果你写的业务逻辑可以这样做)。
2:不用Transaction这个事物注解,事物的提交和回滚自己控制,Sping提供的有事物手动提交和回滚的方法,自己可以查找一下。
文章中很多地方我都标注了那个类的那个方法,跟着上面一步步看完源码,事物方面开发中如果再遇到问题应该很快就能解决,事物那些传播属性也很快就能理解,开发中用嵌套事物也不必太担心有Bug了。
Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题的更多相关文章
- mongodb高级操作及在Java企业级开发中的应用
Java连接mongoDB Java连接MongoDB需要驱动包,个人所用包为mongo-2.10.0.jar.可以在网上下载最新版本. package org.dennisit.mongodb.st ...
- 解决mysql中无法修改事务隔离级别的问题
使用SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;修改数据库隔离级别, 然后执行SELECT @@TX_ISOLATION;后发现数据库的隔离级别并 ...
- 解剖Nginx·模块开发篇(4)模块开发中的命名规则和模块加载与运行流程
1 命名规则 1.1 基本变量 基本变量有三个: ngx_module_t 类型的 ngx_http_foo_bar_module: ngx_command_t 类型的数组 ngx_http_foo_ ...
- spring事务在实际项目开发中的使用
一, 事务的一些基础知识简单回顾一下,讲的不是很深入,网上博客很多. 1,关于事务的四大特性:原子性.隔离性.一致性.隔离性 本文不再赘述: 2,事务的隔离级别:读未提交,读已提交,可重复读,串行 ...
- SQL Server中的事务日志管理(7/9):处理日志过度增长
当一切正常时,没有必要特别留意什么是事务日志,它是如何工作的.你只要确保每个数据库都有正确的备份.当出现问题时,事务日志的理解对于采取修正操作是重要的,尤其在需要紧急恢复数据库到指定点时.这系列文章会 ...
- SQL Server中的事务日志管理的阶梯,级别1:事务日志概述
SQL Server中的事务日志管理的阶梯,级别1:事务日志概述 翻译:刘琼滨 谢雪妮 许雅莉 赖慧芳 级别1:事务日志概述 事务日志是一个文件,其中SQL服务器存储了所有与日志文件关联的数据库执行的 ...
- (转)Spring的bean管理(注解方式)
http://blog.csdn.net/yerenyuan_pku/article/details/69663779 Spring的bean管理(注解方式) 注解:代码中的特殊标记,注解可以使用在类 ...
- 第16周翻译:SQL Server中的事务日志管理,级别3:事务日志、备份和恢复
源自: http://www.sqlservercentral.com/articles/Stairway+Series/73779/ 作者: Tony Davis, 2011/09/07 翻译:刘琼 ...
- iOS开发——高级篇——Runtime实际应用
前言 本篇主要介绍Runtime在开发中的一些使用场景,顺便讲解了下MJExtension的底层实现 一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制, ...
- 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型
小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...
随机推荐
- win7修改开机动画
开机动画的修改 首先win7的过场动画是存在于C:\Windows\System32\bootres.dll ,而修改过程动画就需要修改这个dll,我不会改,所以只能用工具美化大师,软媒魔方里面的一个 ...
- 二分查找-LeetCode704 简单题
LeetCode代码链接:https://leetcode.cn/problems/binary-search/ 题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ...
- 记录一次 MyBatis 批量插入的优化-BatchInsert
记录在一次项目问题排查过程中,遇到在数据量大的情况下,向数据库批量插入非常耗时长的问题. 1.分析 首先,代码是在 service 中,采用的是 for 循环调用 insert 语句的方式: for( ...
- 什么是NineData?突然就火了
NineData 是集成了 SQL 开发.数据复制.数据备份.数据对比多个模块的云服务,支持混合云(自建库+云数据库的业务架构)和多云(多个不同云厂商数据库组成的业务架构)架构下的企业数据管理,大幅降 ...
- express 为所有路由添加 405 method not allowd 响应
背景知识 HTTP Status Code 405 405 Method not allowed The resource was requested using a method that is n ...
- 过两年 JVM 可能就要被 GraalVM 替代了
大家好,我是风筝,公众号「古时的风筝」,专注于 Java技术 及周边生态. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 今天说一说 Graal ...
- 编译安装nmon
nmon 是什么? nmon(Nigel's performance Monitor for Linux)是一种Linux性能监视工具,当前它支持 Power/x86/x86_64/Mainframe ...
- 使用nodejs编写api接口并部署到服务器上
一.用node.js编写api接口 1.安装node环境,没有就去下载nodejs, 下载地址 2.创建一个node项目, 新建一个目录文件,例node_proxy 3.在新建的node项目执行npm ...
- Proxyless Mesh 在 Dubbo 中的实践
背景 随着 Dubbo 3.1 的 release,Dubbo 在云原生的路上又迈出了重要的一步.在这个版本中添加了 Proxyless Mesh 的新特性,Dubbo Proxyless Mesh ...
- [cocos2d-x]关于声音和音效
使用声音音效 CocosDesion实现了简单易用的SimpleAudioEngine类,为了使用它,我们只需引入他的头文件即可: #include "SimpleAudioEngine.h ...