Spring 事务

您所在的位置:网站首页 spring事务代码 Spring 事务

Spring 事务

2024-07-17 00:47| 来源: 网络整理| 查看: 265

目录

一、Spring 事务的使用

1.1、编程式事务(了解即可)

1.2、注解实现声明式事务

1.2.1、@Transactional 注解的使用

1.2.2、参数说明

1.2.3、声明式事务对异常的处理

1.2.3、@Transational 的工作原理

二、Spring 事务的传播机制

2.1、事务传播机制是什么?

2.2、事务的传播机制有什么作用

2.3、事务的传播机制中有哪些?

2.3.1、支持当前调用链上的事务

2.3.2、不支持当前调用链上的事务

2.3.3、嵌套事务

2.4、代码示例

2.4.1、支持当前事务(REQUIRED)示例

2.4.2、嵌套事务示例 

三、Spring 事务失效场景

3.1、访问权限

3.2、方法被 final 修饰

3.3、未被 Spring 管理

3.4、多线程(不在同一个线程下)

3.5、表不支持事务

3.6、事务没有开启

3.7、事务的传播机制

3.8、try catch

3.9、非 RuntimmeException 及其子类异常

3.10、自定义回滚异常

四、嵌套事务导致多回滚,如何解决?

一、Spring 事务的使用 1.1、编程式事务(了解即可)

Spring 编程式事务的使用主要有 3 个步骤:

开启事务(获取事务):通过 Spring Boot 中内置的 DataSourceTransactionManager 的 getTransaction 方法,并搭配内置的 TransactionDefinition 实例作为方法的参数,来获取事务(此操作同时也会开启事务)。提交事务:DataSourceTransactionManager 创建出实例后,使用它的 commit 方法(参数是 getTransaction 方法的返回值,也就是 TransactionStatus,它的本质就是一个事务),就可以完成提交事务。回滚事务:通过 DataSourceTransactionManager 的 rollback 方法(参数就是 事务)进行事务的回滚。

具体的,如果我的业务逻辑是向数据库插入一条数据,如下代码示例:

import com.example.demo.entity.UserInfo; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; //事务管理 @Autowired private DataSourceTransactionManager transactionManager; //事务属性设置(不设置有默认属性) @Autowired private TransactionDefinition transactionDefinition; @RequestMapping("/add") public int insertUserInfo(UserInfo userInfo) { //检验非空 if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } // 1.开启事务 TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); // 2.执行业务逻辑 int result = userService.insertUserInfo(userInfo); System.out.println("添加:" + result + "个用户~"); // // 3.提交事务 // transactionManager.commit(transactionStatus); // 4.回滚事务(根据情况而定) transactionManager.rollback(transactionStatus); return result; } }

Ps:一个事务若已经提交,则不可以进行回滚,否则会报错:“不可多次提交或回滚事务”。

1.2、注解实现声明式事务 1.2.1、@Transactional 注解的使用

在 Spring 提供了 @Transactional 注解实现事务,特点如下:

可以添加在类上或方法上。添加在方法上(方法必须是 public,否则不生效):在方法执行前自动开启事务,方法执行完(没有任何异常)自动提交事务,但如果方法执行期间出现异常,将会自动回滚事务;添加在类上:对所有的 public 方法生效;

具体的如下代码:

@Transactional //声明式事务 @RequestMapping("/add") public Integer add(UserInfo userInfo) { //非空检验 if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) { return 0; } int result = userService.insertUserInfo(userInfo); System.out.println("添加:" + result + "个用户~"); return result; }

1.2.2、参数说明

值得注意的是 isolation 的隔离级别:

读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化 (Serializable)。

1.2.3、声明式事务对异常的处理

@Transactional 在异常被捕获的情况下,不会进行事务的自动回滚,那么如果需求改变,需要在异常被捕获的情况下也进行回滚,该如何实现呢?

方法1:重新抛出异常(有点脱了裤子放屁的感觉),如下代码:

@Transactional //声明式事务 @RequestMapping("/add") public Integer add(UserInfo userInfo) { //插入一条信息到数据库 int result = userService.insertUserInfo(userInfo); try { int i = 1 / 0; } catch(Exception e) { System.out.println(e.getMessage()); throw e; } System.out.println("添加:" + result + "个用户~"); return result; }

方法2:手动回滚事务,如下代码:

@Transactional //声明式事务 @RequestMapping("/add") public Integer add(UserInfo userInfo) { //插入一条信息到数据库 int result = userService.insertUserInfo(userInfo); try { int i = 1 / 0; } catch(Exception e) { System.out.println(e.getMessage()); //手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } System.out.println("添加:" + result + "个用户~"); return result; }

Ps:TransactionAspectSupport表示事务的切面建议、currentTransactionStatus表示获取当前事务、setRollbackOnly表示进行回滚。

1.2.3、@Transational 的工作原理

@Transaction 是基于 AOP 实现的,而 AOP 又是使用动态代理实现的。若目标对象实现了接口,默认使用 JDK 的动态代理,若目标对象没有实现接口,则会使用 CGLIB 动态代理。

@Transational 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。若中途遇到异常,则进行回滚事务。

二、Spring 事务的传播机制 2.1、事务传播机制是什么?

Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法之间进行传递的。

例如这样一种情况:现在有方法一和方法二,方法一有事务,方法二没有事务,方法一会调用到方法二,那么当方法二中出现了异常,方法二需要回滚么?事务的传播机制就是用来解决这种类似的问题~

2.2、事务的传播机制有什么作用

事务的传播机制是保证一个事务在多个调用方法间的可控性。

例如这样一种情况:现在有方法一和方法二,方法一有事务,方法二没有事务,方法一会调用到方法二,那么当方法二中出现了异常,方法二需要回滚么?如果方法二回滚了,那么方法一是否也要跟着回滚?通过事务的传播机制,就会告诉你该如何进行处理~

2.3、事务的传播机制中有哪些?

从大的方向上看,主要分为以下三种~

2.3.1、支持当前调用链上的事务 Propagation.REQUIRED(需要有):默认的事务隔离级别,表示如果当前调用链存在事务,则加入该事务;如果没有事务,则为当前方法创建一个事务。(方法一的事务上添加此属性,意味如果方法二有事务,就加入当前主事务中,如果方法二没有事务,也会为这个方法创建一个事务,并加入到主事务中)Propagation.SUPPORTS(可以有):如果当前调用链存在事务,则加入该事务,如果当前调用链没有事务,就以非事务方式执行。Propagation.MANDATORY(强制有):如果当前调用链存在事务,则加入该事务;如果当前调用链没有事务,则抛出异常。

2.3.2、不支持当前调用链上的事务 Propagation.REQUIRES_NEW:无论当前调用链式上是否存在事务,都会新建一个事务(和主事务互不相干,自己回滚自己的).Propagation.NOT_SUPPORTED:如果当前调用链存在事务,则把当前调用链的事务挂起,也就是说 Propagation.NOT_SUPPORTED 修饰的方法,以非事务的方式运行。Propagation.NEVER:以非事务的方式运行,并且若当前调用链存在事务,则抛出异常。 

2.3.3、嵌套事务

Propagation.NESTED:表示当前调用链存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前调用链没有事务,则等价于 PROPAGATION_REQUIRED(为当前方法创建事务)。

Ps:Propagation.REQUIRES_NEW 和 Propagation.NESTED 区别主要是 NESTED 和主事务是一个事务,而 REQUIRES_NEW 是不同事务。

2.4、代码示例

这里我将用两个示例来说明(这两个如果你能拿捏住,其他的就没问题~),这里的调用链如下图:

2.4.1、支持当前事务(REQUIRED)示例

首先开始事务,使用 UserService 插入一条用户数据,然后再执行(故意制造错误)报错,最后观察执行结果,如下代码:

UserController如下:

import com.example.demo.entity.UserInfo; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Transactional(propagation = Propagation.REQUIRED) @RequestMapping("/insert") public Integer insertUserInfo(UserInfo userInfo) { //插入用户数据 int result = userService.insertUserInfo(userInfo); System.out.println("添加:" + result + "个用户~"); return result; } }

LogService如下:

import com.example.demo.entity.UserInfo; import com.example.demo.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired UserMapper userMapper; public int insertUserInfo(UserInfo userInfo) { int result = userMapper.insertUserInfo(userInfo); int e = 1 / 0; return result; } }

输入 url,插入用户 “zhangsan” 运行结果如下,如下图:

可以观察到成功插入数据后引发的算数异常,按照我们设置的事务,是会进行回滚的,接下来观察数据库验证我们的结果:

结果分析:由于我们设置的事务是REQUIRED,它表示调用链上若存在事务,则加入到事务中,若没有就会为这当前方法创建一个事务,因此在 UserService 方法上即使没有事务,也会加入到当前调用链创建好的事务当中,即使引发了异常,也会进行数据的回滚~

2.4.2、嵌套事务示例 

首先开始事务,使用 UserService 的 insertUserInfo 插入一条用户数据,然后再执行 insert 方法再插入一条用户数据(这里故意引发异常),插入完后报错,最后观察执行结果,如下代码:

UserController如下:

import com.example.demo.entity.UserInfo; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Transactional(propagation = Propagation.REQUIRED) @RequestMapping("/insert") public void insertUserInfo(UserInfo userInfo) { //插入用户数据 int result1 = userService.insertUserInfo(userInfo); int result2 = userService.insert(); } }

UserService如下:

import com.example.demo.entity.UserInfo; import com.example.demo.mapper.UserMapper; import org.apache.catalina.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service public class UserService { @Autowired UserMapper userMapper; public int insertUserInfo(UserInfo userInfo) { int result = userMapper.insertUserInfo(userInfo); return result; } @Transactional(propagation = Propagation.NESTED) public int insert() { UserInfo userInfo = new UserInfo(); userInfo.setUsername("lisi"); userInfo.setPassword("123"); int result = userMapper.insertUserInfo(userInfo); //制造异常 int a = 1 / 0; return result; } }

运行结果如下:

数据库结果如下:

结果分析:在 UserController 中开启事务,UserService 中调用 insertUserInfo 插入一条数据,接着调用 insert 方法(使用 NESTED 嵌套上一个调用类的事务)插入一条数据,插入完后引发异常,于是进行回滚当前事务,因此第二条数据添加失败,又因为是嵌套事务,所以第二条数据添加失败进行回滚之后,会继续向上找调用他的方法和事务,再次进行回滚,因此第一条用户信息也添加失败,最终没用向用户表中添加任何数据~、

三、Spring 事务失效场景 3.1、访问权限

Java 中一个方法的权限为 private,会导致事务失败.  因为 Spring 要求被代理的方法必须是 public.

下图中,在 Kotlin 中就直接指出错误了.

3.2、方法被 final 修饰

在 Java 中,如果一个方法被 final 修饰,事务也会失效.

下图中,Kotlin 就直接指出错误了

原因: 因为 Spring 事务底层是 AOP,他会为目标对象中的方法生成代理逻辑.  这个代理逻辑会在方法调用前后添加事务管理的相关代码(例如,开始事务、提交事务、回滚事务).  然而,如果目标方法被声明为 final,那么动态代理就无法对该对象进行增强,因为 final 方法不能被重写.

3.3、未被 Spring 管理

如果当前方法所在类,并没有被 Spring 注解标注(交给 Spring 管理),事务会失效.

这点就用多说了...

3.4、多线程(不在同一个线程下)

如下代码,add 方法是存在事务的,但是方法中通过多线程调用了 表的删除逻辑,这样就导致两个方法不在同一个线程中,获取到的数据库连接就不一样,因此是不同的事务. 

此时 add 引发异常,remove 逻辑不会回滚.

3.5、表不支持事务

如果表的引擎是 myisam,那么他就是不支持事务的.  要想支持事务,需要改成 innodb 引擎,如下

3.6、事务没有开启

如果是 SpringBoot 项目,那么事务默认是开启的.  但如果是 Spring 项目,需要配置 xml.

3.7、事务的传播机制

如果事务的创博机制设置错了,事务也不会生效

目前只有这几种传播机制会创建新事务:REQUIRED、REQUIRES_NEW、NESTED

3.8、try catch

事务没有回滚,最常见的问题就是:开发者在代码中手动 try catch,如下:

这种情况下,想要让事务生效,需要在 catch 中手动抛出(有种脱了裤子放屁的感觉...)

3.9、非 RuntimmeException 及其子类异常

Spring 事务默认情况下只会回滚 RuntimmeException(运行时异常),和她的子类.  其他的异常类型不会回滚,例如 IOException

@Service class UserService: ServiceImpl() { @Transactional fun remove(id: Long) { this.ktUpdate().eq(User::id, id) .remove() //1.引发异常(会回滚,因为是 RuntimeException 异常的子类) val a = 1 / 0 //2.引发异常(不会回滚,因为 IOException 不是 RuntimeException 的子类) throw IOException() } }

3.10、自定义回滚异常

Spring 事务中,可以通过 @Transactional 自定义回滚异常。但如果程序执行过程中,出现了不是我们自定义回滚异常的类型(包括 sql 异常),事务就不会回滚.

@Transactional(rollbackFor = [IOException::class]) //表示只有 IOException 异常及其子类才会回滚 fun remove(id: Long) { this.ktUpdate().eq(User::id, id) .remove() //这里不会回滚,因为不是我们指定的异常或其子类 throw SQLException("sql 异常!") }

注意,在  @Transactional 中即使指定了回滚异常类型(例如 IOException),但如果引发异常的类型是 RuntimeException 类型(或者子类),事务也会回滚!

RuntimeException (及其子类)是一个特例!

如下,事务也会回滚:

@Transactional(rollbackFor = [IOException::class]) //表示只有 IOException 异常及其子类才会回滚 fun remove(id: Long) { this.ktUpdate().eq(User::id, id) .remove() //即使指定了异常类型为 IOException,但是遇到 RuntimeException 及其子类也会回滚 throw RuntimeException("run 异常!") }

四、嵌套事务导致多回滚,如何解决? @Service class UserService: ServiceImpl() { @Transactional fun handler(id: Long) { add(id) updateById(id) } @Transactional(propagation = Propagation.NESTED) fun updateById(id: Long) { println("处理了一些事情...") } }

上述代码中,如果 handler 中的 add 方法执行成功了,而 updateById 方法执行失败了,那么由于 updateById 是嵌套事务,因此 add 方法也会回滚.

如果我只想让引发异常的 updateById 回滚呢?只需要将引发异常的方法 try catch 一下就 ok,如下:

@Service class UserService: ServiceImpl() { @Transactional fun handler(id: Long) { add(id) try { updateById(id) } catch (e: Exception) { e.printStackTrace() } } @Transactional(propagation = Propagation.NESTED) fun updateById(id: Long) { println("处理了一些事情...") } }



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3