Java框架--Spring(事务,@Transactional)的学习笔记
Spring 事务管理
什么是事务?
- 事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
- 最经典的事务例子当属银行转账,A转账给B一百块的流程要经过两个操作,将A账户减少100块,B账户增加100块,若此时银行系统发生崩溃,可能发生A账户金额减少而B账户金额不增加,造成错误。故事务就是要保证这样的操作要么同时完成,要么同时失败。
事务的四大特性(ACID)
- 原子性:事务是最小的执行单元,不允许分割,事务中的操作要么全部完成,要么都失败。
- 一致性:事务执行前后数据库的状态保持一致,多个事务对同一个数据读取的结果是相同的。
- 隔离性:并发访问数据库时,一个事务的执行不会被另一个事务影响或干扰。
- 持久性:事务执行完成后,对数据库的改变是持久的。即使数据库发生故障也不会影响。
如何保证ACID?
- 数据库中用==锁== 、==并发与多版本== (非阻塞读)保持==一致性== 和==隔离性== ,用事务的commit,rollback,savepoint保持==原子性== ,用数据库文件保持持久性,断电后,内存数据丢失,硬盘文件数据不丢失,重启后从文件中加载到内存,保持==持久性==。
脏读,不可重复读和幻读
- 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个事务回滚(RollBack)了操作,则后一个事务所读取的数据就会是不正确的。
- 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
- 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
事务的隔离等级
- 为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读取未提交(READ- UNCOMMITTED) | √ | √ | √ |
读取已提交(READ- COMMITTED) | × | √ | √ |
可重复读(REPEATABLE- READ) | × | × | √ |
可串行化(SERIALIZABLE) | × | × | × |
- 因为隔离级别越低,事务请求的锁越少,有时候为了性能考虑,不会将隔离级别设置得太高。
- Mysql 默认采用的 REPEATABLE_READ隔离级别。 Oracle默认采用的READ_COMMITTED隔离级别。
事务的传播行为
- 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
- 保证在同一个事务中:
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行。 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
- 保证不在同一个事务里面
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。 |
Spring声明式事务如何工作(Spring的事务管理如何实现)?
原理
- 当Spring容器启动的时候,发现启动类上有@EnableTransactionManagement注解,此时会拦截所有bean的创建,扫描bean上是否有@Transactional注解(类,或者父类,接口,方法上有这个注解都可以),如果有这个注解,Spring会通过AOP的方式生成代理对象,代理对象中会增加一个事务拦截器,拦截器会拦截bean中的public方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务。
执行过程
- Spring事务的底层是基于数据库事务和aop机制的,首先要生成代理对象,然后按照aop的流程来执行具体的逻辑,但事务不是通过通知来实现的,而是通过一个TransactionInterceptor来实现的,然后调用invoke来实现具体的逻辑。
- 解析事务相关的属性,对于使用了@Transactional注解的bean,Spring会启动一个代理对象作为bean。
- 当调用代理对象的方法时,会对其中public 方法(增加了@Transactional注解或者其所属类上加了@Transactional注解)利用事务管理器创建一个数据库连接。
- 修改数据库连接的autoCommit()方法的属性为false,关闭此连接的事务自动提交功能。
- 执行具体的SQL逻辑操作。
- 在执行过程中,如果执行失败了,会通过completeTransactionAfterThrowing方法来完成事务的回滚操作,回滚的具体操作是通过doRollBack方法来实现的,实现的时候也是要获取连接,通过连接对象的RollBack方法来实现回滚。
- 如果执行过程中,没有意外发生,会通过commitTransactionAfterReturning方法来完成事务的提交操作,提交的具体操作是通过doCommit方法来实现的,实现的时候也是要获取连接,通过连接对象的Commit方法来实现事务提交。
- 当事务执行完毕之后需要清除相关事务信息,调用cleanupTransactionInfo方法。
- 注:
- Spring事务的隔离级别对应的就是数据库的隔离级别。
- Spring事务的传播机制是基于数据库连接来做的,一个数据库连接就是一个事务,如果传播机制配置为需要新开一个事务,那么实际上就是先新建一个数据库连接,在此新数据库连接上执行sql。
@Transactional注解
@Transactional注解的属性设置
属性 | 类型 | 描述 |
---|---|---|
value | String | 可限定的描述述符,指定使用的事务管理器 |
propagation | enum:propagation | 可选的事务传播行为设置 |
isolation | enum:Isolation | 可选的事务隔离级别设置 |
readonly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | class 对象数组,必须继承throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollBackFor | Class 对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组。 |
@Transactional注解不开启事务的问题(使用原则)?
- 需要在启动类上添加@EnableTransactionManagement注解,表示开启事务。
- @Transactional这个注解只能加在public方法上面。事务拦截器在目标方法执行前后进行拦截,内部会调用方法来获取Transactional 注解的事务配置信息,调用前会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
- @Transactional对于运行时异常和错误事务会回滚,但是检查异常就不会回滚,此时可以加属性rollbackFor = Exception.class。
- 不要在业务层区捕获异常,在控制器层捕获,否则事务不会回滚。因为异常已经被捕获处理,所以事务便不会回滚。
- 使用该注解需要添加aop的依赖。
- 该注解所属类需要被spring管理。
- 数据库引擎是否支持。确认数据库引擎是否为InnoDB,MYISAM是不支持事务的。
- 同一个类中方法调用,导致@Transactional失效,开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
- 开启多线程任务时,事务管理会受到影响。因为线程不属于spring托管,故线程不能够默认使用spring的事务,也不能获取spring注入的bean在被spring声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.