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. 解决win7设置默认程序打开方式失效

    问题描述 我在设置一个文件(.ui)的默认程序打开,总是失效.设置不成功. 原因 正常这个程序应该用 designer.exe 打开,但是我之前设置过(.ui)默认程序打开程序为designer.ex ...

  2. 谁说.NET没有GC调优?只改一行代码就让程序不再占用内存

    经常看到有群友调侃"为什么搞Java的总在学习JVM调优?那是因为Java烂!我们.NET就不需要搞这些!"真的是这样吗?今天我就用一个案例来分析一下. 昨天,一位学生问了我一个问 ...

  3. 一个小时,200行代码,手写Spring的IOC、DI、MVC

    一.概述 配置阶段:主要是完成application.xml配置和Annotation配置. 初始化阶段:主要是加载并解析配置信息,然后,初始化IOC容器,完成容器的DI操作,已经完成HandlerM ...

  4. Rust 学习之旅(7):Package,Crate,Module

    Rust 学习之旅(7):Package,Crate,Module 这是第 7 章的读书笔记,Cargo Workspace 在第 14 章. Packages and Crates As a pro ...

  5. JAVA学到方法写了一个四则运算计算器,请教一下有什么需要改进的

    package method; /* * 四则运算计算器 * */ import java.util.Scanner; public class Demo07 { public static void ...

  6. Java程序员除了做增删改查还能干嘛?

    就以Java后端开发为例,说说不同级别程序员干的事情. 1 初级开发,大概是有3年Java开发经验. 22年底,上海,这批程序员如果学历是本科,薪资一般是8k到2w,当然如果能进好公司或互联网大厂,薪 ...

  7. 用Dockerfile制作一个java应用镜像,ubuntu基础篇

    内容介绍: (1) 本章目的,将一个自行开发的java程序webpay-api,制作为docker自定义镜像,并且进行部署. (2) 实验环境: 物理机:VMware 虚拟机 + CentOS 7.8 ...

  8. 第七节 VOR/DME进近程序保护区的绘制

    飞行程序设计软件实践 通过前面六节的练习,2023社区版插件的主要功能都已经使用到了.今天通过VOR/DME非精密进近程序,再将这些功能串起来使用一下.今天的软件,我们使用浩辰CAD2023版(过期后 ...

  9. 一文搞定Spring Task

    今天和大家分享下Spring Task这个知识点,主要通过下面6个点来展开描述,希望能够帮助到大家. 1.什么是定时任务 2.入门案例 3.Corn表达式 4.Corn实战案例 5.@Schedule ...

  10. App几个可能造成内存泄漏的情况:

    App几个可能造成内存泄漏的情况: 1.block块中直接用self调用,self将会被block copy到内部增加一次饮用计数,形成循环引用 在block里调用self会不会造成循环引用和这个bl ...