@Transactional注解
概念介绍
当出现异常情况时,可以保证数据的⼀致性;
Spring⽀持两种事物⽅式:
编程式事物:使⽤的是TransactionTemplate(或者ansaction.PlatformTransactionManager接⼝)
声明式事物:使⽤Transactional注解或者xml配置,建⽴在AOP之上的。本质就是对⽅法前后进⾏拦截,然后在⽬标⽅法开始之前创建或者添加⼀个事物,在执⾏完⽬标⽅法之后根据执⾏情况提交或者回滚事物;
声明式事物的优缺点:
使正常的业务代码不受污染,是spring倡导的⾮⼊侵开发⽅式,简直不要太⽅便哦;
缺点:最细的颗粒度也只能做到⽅法级别的,⽆法做到代码块级别;有这样的需求,我们将代码块⽅到⽅法⾥就好了;
⾃动提交(autoCommit)
默认的情况下,数据库是处于⾃动提交模式的,每⼀条语句处于⼀个单独的事物中,执⾏完了就提交;但是在实际的业务开发的事物管理中,⼀般是⼀组相关的操作都放在同⼀个事物之中,因此,必须关闭数据库的⾃动提交模式,Spring会将底层的⾃动提交属性设置为false(DataSourceTransactionManager.java中);
某些数据库链接池提供了关闭事物的⾃动提交设置,最好的是在链接池就将其关闭,但是C3P0不⽀持这⼀设置;
连接关闭时的是否⾃动提交
当⼀个连接关闭时,如果有未提交的事务应该如何处理?JDBC规范没有提及,C3P0默认的策略是回滚任何未提交的事务。
这是⼀个正确的策略,但JDBC驱动提供商之间对此问题并没有达成⼀致。
C3P0的autoCommitOnClose属性默认是false,没有⼗分必要不要动它。或者可以显式的设置此属性为false,这样会更明确。
Mybatis与Spring的事物整合:
MyBatis⾃动参与到spring事务管理中,⽆需额外配置,只要batis.spring.SqlSessionFactoryBean引⽤的数据源与DataSourceTransactionManager引⽤的数据源⼀致即可,否则事务管理会不起作⽤。
事务的隔离级别(由TransactionDefinition接⼝定义):
ISOLATION_DEFAULT:这是默认值,表⽰使⽤底层数据库的默认隔离级别。对⼤部分数据库⽽⾔,通常这值就是
ISOLATION_READ_COMMITTED。
ISOLATION_READ_UNCOMMITTED:该隔离级别表⽰⼀个事务可以读取另⼀个事务修改但还没有提交的数据。该级别不能防⽌脏读,不可重复读和幻读,因此很少使⽤该隔离级别。⽐如PostgreSQL实际上并没有此级别。
ISOLATION_READ_COMMITTED:该隔离级别表⽰⼀个事务只能读取另⼀个事务已经提交的数据。该级别可以防⽌脏读,这也是⼤多数情况下的推荐值。
ISOLATION_REPEATABLE_READ:该隔离级别表⽰⼀个事务在整个过程中可以多次重复执⾏某个查询,并且每次返回的记录都相同。该级别可以防⽌脏读和不可重复读。
ISOLATION_SERIALIZABLE:所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。
为什么会发⽣隔离级别这个概念? -- 因为数据库并发事务的存在
脏读/幻读/不可重复读(从A事务的⾓度看)
脏读:A事物读取了B事物尚未提交的数据。---取款例⼦;
不可重复读:A事物读取了B事物已经提交的更改数据(注意:是更改数据)。
解决的⽅式是:加⾏级锁,防⽌数据变化;
幻读:A事物读取了B事物提交的新增数据(注意:是新增数据)。
解决的⽅法:加表级锁,防⽌新增数据;
数据库并发事物引起的其他问题 -- :(从B事务的⾓度看)
第⼀类丢失更新:A事物撤销时,将B事物已经提交的更新数据覆盖了;
第⼆类丢失更新:A事物提交时,将B事务已经提交的跟新数据覆盖了;
为了解决上述问题,数据库通过锁机制解决并发访问的问题。根据锁定对象不同:分为⾏级锁和表级锁;根据并发事务锁定的关系上看:分为共享锁定和独占锁定,共享锁定会防⽌独占锁定但允许其他的共享锁定。⽽独占锁定既防⽌共享锁定也防⽌其他独占锁定。为了更改数据,数据库必须在进⾏更改的⾏上施加⾏独占锁定,insert、update、delete和selsct for update 语句都会隐式采⽤必要的⾏锁定。
但是直接使⽤锁机制管理是很复杂的,基于锁机制,数据库给⽤户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的sql语句然后⾃动选择合适的锁
注意:事务的隔离级别和并发处理性能是成反⽐的;
事务的传播⾏为:当前事务开启之前,⼀个事务已经存在了,此时我们可配置若⼲选项来指定当前新事务的执⾏⾏为(TransactionDefinition定义接⼝定义)
PROPAGATION_REQUIRED:如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。这是默认值。
PROPAGATION_REQUIRES_NEW:创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。
PROPAGATION_SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
PROPAGATION_NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
PROPAGATION_MANDATORY:如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
事务超时:指当前事务允许执⾏的最长时间,超时未完成则回滚;
在TransactionDefinition中以int值来表⽰超时时间,单位秒,未完成则回滚,注解中的属性是timeout;
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性:代码只读但不修改的属性;readOnly = true
Spring 的事务回滚规则:
指⽰spring事务管理器回滚⼀个事务的推荐⽅法是在当前事务的上下⽂内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运⾏时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException 的⼦类(Errors也会导致事务回滚),⽽抛出checked异常则不会导致事务回滚。
checked: ⼀般是指程序不能直接控制的外界情况,是指在编译的时候就需要检查的⼀类exception,⽤户程序中必须采⽤try catch机制处理或者通过throws交由调⽤者来处理。这类异常,主要指除了Error以及RuntimeException及其⼦类之外的异常。
unchecked:是指那些不需要在编译的时候就要处理的⼀类异常。在java体系⾥,所有的Error以及RuntimeException 及其⼦类都是unchecked异常。再形象直⽩的理解为不需要try catch等机制处理的异常,可以认为是unchecked的异常。
spring aop应用场景
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
还可以编程性的通过setRollbackOnly()⽅法来指⽰⼀个事务必须回滚,在调⽤完setRollbackOnly()后你
所能执⾏的唯⼀操作就是回滚。
@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 注解的⽤法:
可以作⽤于接⼝、接⼝⽅法、类以及类⽅法上。当作⽤于类上时,该类的所有 public ⽅法将都具有该类型的事务属性,同时,我们也可以在⽅法级别使⽤该标注来覆盖类级别的定义。
Spring 建议不要在接⼝或者接⼝⽅法上使⽤该注解;为什么呢?
因为此时只⽤在使⽤基于接⼝的代理时才会⽣效;
只被应⽤到 public ⽅法上,由 Spring AOP 的本质决定。如果在 protected、private 或者默认可见性的⽅法上使⽤将被忽略,也不会抛出任何异常。
类内部⽅法调⽤本类内部的其他⽅法并不会引起事务⾏为
原因:
SpringAop的实现⽅式由两种:java代理(优点:不⽤依赖第三⽅jar包,但只能代理接⼝,不能代理类)和Cglib动
态增强,这两种⽅式在Spring中⽆缝切换。
Spring通过AopProxy(['prɒksɪ])接⼝抽象了这两种实现,实现了⼀致的AOP⽅式;(AopProxy:
[jdkDynamicAopProxy, CglibAopProxy])
Spring的aop实现时:以CgLIb⽅式增强的AOP⽬标类,会创建⼀个Bean实例本⾝和⼀个Cglib增强代理对象;这是
因为Spring会抹杀CgLib能够直接创建普通类的增强⼦类的能⼒,他把CgLib动态⽣成的⼦类当成了普通的代理类;
此时,我们来梳理下SpringAop代理类的实际调⽤过程
客户端通过applicationContext获得代理类的调⽤,代理类在调⽤targetBean
这样的后果是:当你在targetBean中再次调⽤内部⽅法时,他就不会⾛到AopProxy代理类,⽽Transactional注解
的实现是严格依赖于Aop的通知的,所以,我们看到的效果是,在同⼀个类的内部调⽤事务注解的⽅法时,事务注解
不⽣效;其实不仅仅是事务的通知,所⽤利⽤Spring实现的Aop的通知,都同样会受到限制;
问题场景
问题⼀:同⼀个类中⼀个普通⽅法调⽤另⼀个有事务的⽅法,这时候,事务时会不会起作⽤?
思考为什么??
@Transactional的事务开启 ,或者是基于接⼝的或者是基于类的代理被创建。所以在同⼀个类中⼀个⽅法调⽤另⼀个⽅
法有事务的⽅法,事务是不会起作⽤的
解决⽅法:将事务⽅法放⼊另⼀个类中;
问题⼆:
@Transactional 注解只能应⽤到 public 可见度的⽅法上。其他可见度的的⽅法上使⽤,它也不会报错,事务也不⽣效。
问题三:
当注释的⽅法的代码被Try了,那么catch中必须throw异常,否则⽆法rollback;
问题四;
⽅法A声明事务为required,A中调⽤⽅法B,C(B,C均为required),⽅法C抛异常,最后事务的执⾏情况?
全回滚了
问题五:
⽅法A声明事务为required,A中调⽤⽅法B,⽅法B是REQUIRES_NEW,此时⽅法B抛异常了,最后的事务执⾏情况?
均回滚
问题六:
⽅法A声明事务为required,A中调⽤⽅法B,C(B为required,C为REQUIRES_NEW),此时C⽅法抛异常,最后事务的执⾏情况?
全回滚
问题七:
⽅法A声明事务为required,A中调⽤⽅法B,C(B为REQUIRES_NEW,C为required),此时C⽅法抛异常,
最后事务的执⾏情况?
⽅法B事务执⾏,⽅法A事务回滚;
transactionManager && gaotuTransactionManager
transactionManager --> super_class
gaotuTransactionManager --> gaotu
@Transactional 万恶的根源均来⾃于SpringAop的局限性;
转⾃: