序言
在刚刚开始开发的时候,我仅仅只是偶尔看到有关事务管理相关的字眼,后来慢慢的知道了当我们在开发时在执行某些代码时遇到了错误,本应一同执行完成的代码却只有一部分执行完成了,造成数据库中的数据有问题,需要在业务层的类方法上添加事务管理的配置,这种配置可以是注解形式或者是xml配置文件形式。
再来后我略微深入了一下,知道了原来spring的事务管理机制是基于动态代理模式的,每一个被事务管理的方法都是通过代理类中方法以try、catch方式来实现的,在代理类中会进行事务的开启、回滚与提交,如果被代理的方法抛错了,那么就回滚,如果没有抛错正常运行则提交事务。
在日志开发中要注意这几个点:
1:我们目标类中被代理的方法不能自己try、catch了,要把错误抛出来,这样代理类中方法才能正常的进行事务的管理
2:还有被代理的方法需要申明为public,因为如果你申明成了其它三种访问权限,代理类调用不了目标类中的目标方法呀。
3:spring默认的事务管理代理模式是cglib动态代理,也就是针对具体实现类进行动态代理,这就需要我们把事务注解只能加到具体实现方法上而不是接口层面的方法了,当前你也可以修改spring的事务管理代理模式为jdk动态代理,这样就能把注解加到接口层的方法上了,不过spring团队推荐还是使用cglib动态代理。
但是学习还需要更加深入,只停留在会用的层面上哪些,于是开始更加深入学习总结事务相关知识。
先来了解下什么是事务?
小明去银行取钱,取钱分为两个步骤,第一步是银行扣除小明卡上的余额,第二步是把钱给小明。这个过程中如果取款机出错了,那么可能就导致银行扣了小明卡上的余额而小明没有拿到应得的钱,正确的流程是取款机出错了,小明卡上的余额也不会变少。小明取钱的过程可以理解为一个事务,如果事务在执行过程中出错了,那么就需要进行回滚以保证数据的完整性和一致性。
接下来介绍下事务的相关的知识点
一:事务的特性
事务有4个特性:ACID
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
二:事务的传播机制(propagation)
我们一般在项目的业务层进行事务的控制,在业务层中开启了事务的方法之间的调用需要指定传播机制,不指定就是默认的。
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务(默认的事务传播机制) |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
三:事务的隔离机制(isolation)
隔离级别 | 含义 |
isolation_default | 使用数据库默认的事务隔离级别 |
isolation_read_uncommitted | 允许读取尚未提交的修改,可能导致脏读、幻读和不可重复读 |
isolation_read_committed | 允许从已经提交的事务读取,可防止脏读、但幻读,不可重复读仍然有可能发生 |
isolation_repeatable_read | 对相同字段的多次读取的结果是一致的,除非数据被当前事务自生修改。可防止脏读和不可重复读,但幻读仍有可能发生 |
isolation_serializable | 串行化的执行事务,完全服从acid隔离原则,确保不发生脏读、不可重复读、和幻读,但执行效率最低 |
四:事务的超时设置
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
五:事务的只读配置
声明式事务的第三个特性是它是否是一个只读事务。如果一个事务只对后端数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化 措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。
六:事务的回滚机制
事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
下面是在service层常用的事务注解及其内部属性介绍
/**
* 1.添加事务注解
* 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。
* 默认取值为REQUIRED,即使用调用方法的事务
* REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。
*
* 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED
* 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。
* 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true
* 5.使用timeOut 指定强制回滚之前事务可以占用的时间,单位是秒。
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, timeout=3)
七:事务的实现原理
Spring默认使用cglib动态代理,我们将需要进行事务管理的业务处理service实现类添加 @Transactional注解,Spring框架在初始化时会扫描到@Transactional注解,给其添加声明式事务AOP环绕功能增强,