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)管理高级篇一栈式解决开发中遇到的事务问题的更多相关文章

  1. mongodb高级操作及在Java企业级开发中的应用

    Java连接mongoDB Java连接MongoDB需要驱动包,个人所用包为mongo-2.10.0.jar.可以在网上下载最新版本. package org.dennisit.mongodb.st ...

  2. 解决mysql中无法修改事务隔离级别的问题

    使用SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;修改数据库隔离级别, 然后执行SELECT @@TX_ISOLATION;后发现数据库的隔离级别并 ...

  3. 解剖Nginx·模块开发篇(4)模块开发中的命名规则和模块加载与运行流程

    1 命名规则 1.1 基本变量 基本变量有三个: ngx_module_t 类型的 ngx_http_foo_bar_module: ngx_command_t 类型的数组 ngx_http_foo_ ...

  4. spring事务在实际项目开发中的使用

      一, 事务的一些基础知识简单回顾一下,讲的不是很深入,网上博客很多. 1,关于事务的四大特性:原子性.隔离性.一致性.隔离性 本文不再赘述: 2,事务的隔离级别:读未提交,读已提交,可重复读,串行 ...

  5. SQL Server中的事务日志管理(7/9):处理日志过度增长

    当一切正常时,没有必要特别留意什么是事务日志,它是如何工作的.你只要确保每个数据库都有正确的备份.当出现问题时,事务日志的理解对于采取修正操作是重要的,尤其在需要紧急恢复数据库到指定点时.这系列文章会 ...

  6. SQL Server中的事务日志管理的阶梯,级别1:事务日志概述

    SQL Server中的事务日志管理的阶梯,级别1:事务日志概述 翻译:刘琼滨 谢雪妮 许雅莉 赖慧芳 级别1:事务日志概述 事务日志是一个文件,其中SQL服务器存储了所有与日志文件关联的数据库执行的 ...

  7. (转)Spring的bean管理(注解方式)

    http://blog.csdn.net/yerenyuan_pku/article/details/69663779 Spring的bean管理(注解方式) 注解:代码中的特殊标记,注解可以使用在类 ...

  8. 第16周翻译:SQL Server中的事务日志管理,级别3:事务日志、备份和恢复

    源自: http://www.sqlservercentral.com/articles/Stairway+Series/73779/ 作者: Tony Davis, 2011/09/07 翻译:刘琼 ...

  9. iOS开发——高级篇——Runtime实际应用

    前言 本篇主要介绍Runtime在开发中的一些使用场景,顺便讲解了下MJExtension的底层实现 一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制, ...

  10. 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型

    小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...

随机推荐

  1. 6、将两个字符串连接起来,不使用strcat函数

    /* 将两个字符串连接起来,不使用strcat函数 */ #include <stdio.h> #include <stdlib.h> void strCat(char *pS ...

  2. 1759D(数位变0)

    题目链接 题目大意: 给你两个整数n, m.你需要求一个数,它满足如下条件: 是n的整数倍,且倍数小于m. 你应该使其末尾的0尽可能的多(如100后面有2个零,1020后面有一个零,我们应该输出100 ...

  3. 比 JSON.stringify 快两倍的fast-json-stringify

    前言 相信大家对JSON.stringify并不陌生,通常在很多场景下都会用到这个API,最常见的就是HTTP请求中的数据传输, 因为HTTP 协议是一个文本协议,传输的格式都是字符串,但我们在代码中 ...

  4. 异构混排在vivo互联网的技术实践

    作者:vivo 互联网算法团队- Shen Jiyi 本文根据沈技毅老师在"2022 vivo开发者大会"现场演讲内容整理而成. 混排层负责将多个异构队列的结果如广告.游戏.自然量 ...

  5. 从Qt到C#,通过COM组件达成跨语言跨平台链接,或者说从托管到非托管的思路

    从Qt到C#,通过COM组件达成跨语言跨平台链接,或者说从非托管到托管 写在前面 c#真的是一种非常蛋疼的语言,和别的语言兼容性差,界面开发效率也不是很高,但是胜在库功能强大,对windows的兼容好 ...

  6. 未授权访问漏洞之Redis漏洞复现

    前言 未授权访问漏洞简写是SSRF(Server-Side Request Forgery:服务器端请求伪造),是一种服务器端提供了可以从其他服务器获取资源和数据的功能,但没有对目标地址进行过滤和限制 ...

  7. 浅谈 C++ 模板 & 泛化 (妈妈再也不用担心我不会用 std::sort 了)

    基础复习 先上个对 int 类型数组的插入排序: void insertionSort_01(int* seq, int firstIndex, int lastIndex) { for (int j ...

  8. 2022年8月20,第一组,周鹏,从1到m中随机取n个数,n<=m,显示出所有取法

    static Random a1 =new Random(); static int m = a1.nextInt(20)+1;//随机取一个1到20的值 static int n = a1.next ...

  9. Python实验报告(第7章)

    实验7:面向对象程序设计 一.实验目的和要求 1.了解面向对象的基本概念(对象.类.构造方法): 2.学会类的定义和使用: 3.掌握属性的创建和修改: 4.掌握继承的基本语法. 二.实验环境 软件版本 ...

  10. 经典 backbone 总结

    目录 目录 VGG ResNet Inceptionv3 Resnetv2 ResNeXt Darknet53 DenseNet CSPNet VoVNet 一些结论 参考资料 VGG VGG网络结构 ...