事务(Transaction)是指一组要么全部成功,要么全部失败的操作。
一个事务通常由以下四个基本特性组成:
原子性(Atomicity):一个事务中的所有操作要么全部执行成功,要么全部回滚失败,即事务的执行结果是“要么都做,要么都不做”。
一致性(Consistency):事务完成后,数据库所处的状态应该是一致的,也就是说,事务应该满足一定的业务规则,如果事务执行失败,则会回滚之前的操作,以确保数据库的一致性。
隔离性(Isolation):多个事务同时访问数据库时,每个事务都应该被隔离开来,相互之间不会产生影响。这种隔离性可以避免并发访问时的脏读、不可重复读和幻读等问题。
持久性(Durability):事务执行成功后,对数据库所做的更改应该被永久地保存在数据库中,即使系统发生故障,也应该能够通过恢复机制,将数据恢复到它们的原始状态。
回顾mysql中的隔离
在使用时,会声明式事务自动提交 。在方法执行前自动开启事务,在方法执行完(无异常)自动提交事务;如果在执行期间出现异常就自动回滚事务 。
@Transactional
注解只对公共(public)方法有用。
方法级别:将
@Transactional
注解应用于方法上可以指定该方法需要进行事务管理。当方法被调用时,事务将被启动,并在方法执行结束后根据结果进行提交或回滚。在方法级别上使用@Transactional
注解可以使得方法成为一个原子操作,要么全部成功,要么全部失败。类级别:将
@Transactional
注解应用于类上时,表示该类中的所有公共方法都受到事务管理的控制。这种方式适用于一个类的所有方法都需要相同的事务管理策略的情况。可以通过在类级别上使用@Transactional
注解来避免在每个方法上都重复添加注解。接口级别:将
@Transactional
注解应用于接口上时,表示该接口的所有实现类中的方法都受到事务管理的控制。这种方式适用于多个实现类共享相同的事务管理策略的情况。
先来看例子
@Transactional()//声明式事务自动提交 //在方法执行前自动开启事务,在方法执行完(无异常)自动提交事务;如果在执行期间出现异常就自动回滚事务,就比如程序中写入算数异常; @RequestMapping("/insert") public Integer insert(UserInfo userinfo){ if(userinfo==null||!StringUtils.hasLength(userinfo.getName())){ return 0; } int result=userService.add(userinfo); System.out.println("添加insert:"+result); try { int num=10/0; } catch (Exception e) { System.out.println(e.getMessage()); } return result; }
select* from userinfo; +-----+-------------+------+------------------+ | id | name | age | email | +-----+-------------+------+------------------+ | 1 | John | 22 | [email protected] | | 2 | 小李子zi | 30 | [email protected] | | 3 | 李华 | 11 | [email protected] | | 4 | 孙悟空 | 90 | [email protected] | | 5 | 唐僧 | 1000 | [email protected] | | 6 | 孙悟空 | 30 | [email protected] | | 7 | smith | 888 | [email protected] | | 8 | 猪八戒 | 1999 | NULL | | 9 | 小龙女 | 30 | [email protected] | | 100 | www | NULL | NULL | | 101 | 小美 | NULL | NULL | +-----+-------------+------+------------------+ 11 rows in set (0.00 sec)
问题描述:在@Transactional注解的类中即使出现了int num=10/0;算术异常,但是通过ctrl+alt+t生成try catch捕获异常,仍然成功提交了而没有回滚!这就是问题所在!!!
try-catch将异常自行处理了,程序就感知不到异常!
解决方法:
1. 将异常继续抛出
try { int num=10/0; } catch (Exception e) { // System.out.println(e.getMessage()); //1.继续抛出异常 throw e; }
但是觉得这种方式必要性不大,还不如不写 try-catch
2.手动回滚事务
try { int num=10/0; } catch (Exception e) { // System.out.println(e.getMessage()); //1.继续抛出异常 // throw e; //2.使用代码手动回滚事务----setRollbackOnly() System.out.println(e.getMessage()); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }
@Transactional基于AOP实现的,AOP基于动态代理实现,目标对象如果使用了接口,会默认使用jdk代理,如果目标对象没有实现接口,会使用CGlib动态代理。@Transactional开始执行业务之前,通过代理开启事务,执行成功后提交事务,如果程序遇到异常就会回滚事务。
挂起是指将当前事务挂起,执行一个非事务操作,然后恢复事务的过程。
假设你正在玩一个多人在线游戏,而你正在进行一次重要的任务,比如打怪、收集物品或完成任务。突然,有人敲门,你需要去开门,但你又不想中断任务。这时候,你可以选择挂起当前任务,去开门,然后再回来继续任务的执行。
在这个例子中,任务执行就是事务的执行,而开门的过程则是非事务的操作。通过挂起当前任务,你可以先处理非事务的操作,然后再回到原来的任务继续执行,而不会丢失或中断任务的进度。
在数据库中,挂起操作也类似于这个例子。当一个事务需要执行某个非事务的操作(比如访问外部资源、调用其他服务等),为了不中断当前事务,可以将当前事务挂起,执行非事务的操作,然后再回到原来的事务继续执行。
总结来说,挂起是一种暂停当前事务的操作,并执行一个非事务的操作,然后再恢复事务的执行。这样可以保证事务的完整性和一致性,同时处理一些并发的非事务操作。
在多个事务之间传递和共享事务上下文的过程
有两个事务:一个是用户将商品添加到购物车的事务(事务A),另一个是用户提交订单的事务(事务B)。在事务A执行的过程中,它将商品添加到了购物车,并生成了相应的购物车记录。而在事务B执行的过程中,它需要从购物车中读取商品记录并生成订单。
这时候就需要使用到融入的概念。当事务B开始执行时,它需要融入到事务A的上下文中,以便能够读取到事务A中已经添加到购物车的商品记录。融入操作会保证事务B能够访问到事务A的数据,从而正确地生成订单。
简单来说,融入就是将多个事务紧密连接起来,使得后续事务能够共享前一事务的上下文和数据,确保数据的一致性和完整性
REQUIRED:如果当前存在事务,则加入该事务;否则新建一个事务。
SUPPORTS:支持当前事务,如果不存在则以非事务方式执行。
MANDATORY:强制使用当前事务,如果不存在则抛出异常。
REQUIRES_NEW:创建一个新的独立事务,如果当前存在事务,则挂起当前事务。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起当前事务。
NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则在该事务内部嵌套一个新的事务执行,否则执行与REQUIRED相同的操作。
嵌套事务是指在一个事务中开启另一个事务的过程,并将它们嵌套在一起执行。嵌套事务与其他事务相互独立,但依赖于它们的父级事务。
假设你正在编写一个银行系统的应用程序,其中有两个功能:转账和日志记录。转账功能用于将金额从一个账户转移到另一个账户,而日志记录功能用于记录转账操作的日志。
现在,你可能需要设计这样一个场景:在每次转账时,都要先将转账金额从一个账户减少,然后将金额增加到另一个账户,并在数据库中记录一条日志。这个过程需要保证转账和日志记录的一致性,即只有当转账和日志记录都成功时,才能确认整个事务的提交。
在这种情况下,你可以将整个转账和日志记录过程看作是一个嵌套事务。外层事务是整个转账过程的主事务,内层事务是日志记录操作的子事务。当外层事务开始时,会开启一个新的内层事务,然后依次执行转账和日志记录操作,最后根据结果决定是否提交或回滚整个事务。
特别需要注意的是,在嵌套事务中,内层事务的提交或回滚不会影响外层事务的状态。即使内层事务回滚,外层事务仍然可以决定是否提交。
事务的隔离级别是为了解决在并发访问数据库时可能出现的问题。在多个线程同时访问数据库时,如果不进行隔离处理,可能会出现以下几种问题:
1. 脏读:一个事务读取了另一个事务未提交的数据。
2. 不可重复读:一个事务多次读取同一数据,在这个过程中另一个事务修改了这条数据,导致前一个事务读取到不同的数据。
3. 幻读:一个事务多次读取同一范围的数据,在这个过程中另一个事务插入了新的数据,导致前一个事务读取到了新插入的数据。
因此,事务的隔离级别可以控制一个事务对于另一个事务的影响程度,从而保证并发访问数据库时的数据完整性和一致性。常见的事务隔离级别有:
1. READ UNCOMMITTED(读未提交):一个事务可以读取另一个未提交的数据,可能导致脏读、不可重复读和幻读等问题。
2. READ COMMITTED(读已提交):一个事务只能读取另一个已提交的数据,可以避免脏读问题,但是在多次读取同一数据过程中仍然可能出现不可重复读和幻读。
3. REPEATABLE READ(可重复读):一个事务只能在自己完成之前读取已提交的数据,可以避免脏读和不可重复读问题,但是在多次读取同一范围的数据过程中仍然可能出现幻读。
4. SERIALIZABLE(串行化):将多个事务串行执行,完全避免了脏读、不可重复读和幻读问题,但是对数据库性能的影响最大。
需要注意的是,不同的隔离级别会导致不同的性能和数据完整性权衡。
在Spring框架中,可以使用`@Transactional`注解来配置事务的隔离级别。`@Transactional`注解提供了一个isolation属性,用于指定事务的隔离级别。常见的事务隔离级别与对应的常量值如下:
1.Isolation. DEFAULT :以连接的数据库的事务隔离级别为主。
2. Isolation . READ _ UNCOMMITTED :读未提交,可以读取到未提交的事务,存在脏读。
3.Isolation. READ _ COMMITTED :读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。4.Isolation. REPEATABLE _ READ :可重复读◇解决了不可重复读,但存在幻读( MySQL 默认级别)。
5.Isolation. SERIALIZABLE :串行化,可以解决所有并发问题,但性能太低。
从上述介绍可以看出,相比于 MySQL 的事务隔离级别, Spring 的事务隔离级别只是多了一个 Isolation . DEFAULT (以数据库的全局事务隔离级别为主)。
可以在使用`@Transactional`注解时通过`isolation`属性来指定事务的隔离级别。例如:
@Transactional(isolation = Isolation.READ_COMMITTED) public void someTransactionalMethod() { // 事务操作代码 }