引言java
@Transactional
注解相信你们并不陌生,平时开发中很经常使用的一个注解,它能保证方法内多个数据库操做要么同时成功、要么同时失败。使用@Transactional
注解时须要注意许多的细节,否则你会发现@Transactional
老是莫名其妙的就失效了。面试
下面咱们从what ,where,when,四个方面完全弄明白如何回答面试官的问题。spring
事务(Transaction),通常是指要作的或所作的事情。在计算机术语中是指访问并可能更新数据库中各类数据项的一个程序执行单元(unit)。数据库
这里咱们以取钱的例子来说解:好比你去ATM机取1000块钱,大致有两个步骤:第一步输入密码金额,银行卡扣掉1000元钱;第二步从ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。若是银行卡扣除了1000块可是ATM出钱失败的话,你将会损失1000元;若是银行卡扣钱失败可是ATM却出了1000块,那么银行将损失1000元。编程
如何保证这两个步骤不会出现一个出现异常了,而另外一个执行成功呢?事务就是用来解决这样的问题。事务是一系列的动做,它们综合在一块儿才是一个完整的工做单元,这些动做必须所有完成,若是有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过同样。在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。app
在咱们平常开发中事务分为声明式事务和编程式事务。性能
编程式事务ui
是指在代码中手动的管理事务的提交、回滚等操做,代码侵入性比较强。this
编程式事务指的是经过编码方式实现事务,容许用户在代码中精肯定义事务的边界。编码
即相似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。
对于编程式事务管理,spring推荐使用TransactionTemplate。
try { //TODO something transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw new InvoiceApplyException("异常"); }复制代码
声明式事务
管理创建在AOP之上的。其本质是对方法先后进行拦截,而后在目标方法开始以前建立或者加入一个事务,在执行完目标方法以后根据执行状况提交或者回滚事务。
声明式事务最大的优势就是不须要经过编程的方式管理事务,这样就不须要在业务逻辑代码中掺琐事务管理的代码,只需在配置文件中作相关的事务规则声明(或经过基于@Transactional注解的方式),即可以将事务规则应用到业务逻辑中。
简单地说,编程式事务侵入到了业务代码里面,可是提供了更加详细的事务管理;
而声明式事务因为基于AOP,因此既能起到事务管理的做用,又能够不影响业务代码的具体实现。
声明式事务也有两种实现方式,一是基于TX
和AOP
的xml配置文件方式,二种就是基于@Transactional注解了。
@GetMapping("/user") @Transactional public String user() { int insert = userMapper.insert(userInfo); } 复制代码
@Transactional 能够做用在接口
、类
、类方法
。
public
方法都配置相同的事务属性信息。@Transactional @RestController @RequestMapping public class MybatisPlusController { @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class) @GetMapping("/user") public String test() throws Exception { User user = new User(); user.setName("javaHuang"); user.setAge("2"); user.setSex("2"); int insert = userMapper.insert(cityInfoDict); return insert + ""; } } 复制代码复制代码
propagation
表明事务的传播行为,默认值为 Propagation.REQUIRED
,其余的属性信息以下:
Propagation.REQUIRED
:若是当前存在事务,则加入该事务,若是当前不存在事务,则建立一个新的事务。( 也就是说若是A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 ) Propagation.SUPPORTS
:若是当前存在事务,则加入该事务;若是当前不存在事务,则以非事务的方式继续运行。Propagation.MANDATORY
:若是当前存在事务,则加入该事务;若是当前不存在事务,则抛出异常。Propagation.REQUIRES_NEW
:从新建立一个新的事务,若是当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED
模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW
模式,而后在 a 方法中调用 b方法操做数据库,然而 a方法抛出异常后,b方法并无进行回滚,由于Propagation.REQUIRES_NEW
会暂停 a方法的事务 ) Propagation.NOT_SUPPORTED
:以非事务的方式运行,若是当前存在事务,暂停当前的事务。Propagation.NEVER
:以非事务的方式运行,若是当前存在事务,则抛出异常。Propagation.NESTED
:和 Propagation.REQUIRED 效果同样。isolation
:事务的隔离级别,默认值为 Isolation.DEFAULT
。
TransactionDefinition.ISOLATION_DEFAULT
这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,一般这值就是
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
该隔离级别表示一个事务能够读取另外一个事务修改但尚未提交的数据。该级别不能防止脏读,不可重复读和幻读,所以不多使用该隔离级别。好比PostgreSQL实际上并无此级别。
TransactionDefinition.ISOLATION_READ_COMMITTED
该隔离级别表示一个事务只能读取另外一个事务已经提交的数据。该级别能够防止脏读,这也是大多数状况下的推荐值。TransactionDefinition.ISOLATION_REPEATABLE_READ
该隔离级别表示一个事务在整个过程当中能够屡次重复执行某个查询,而且每次返回的记录都相同。该级别能够防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE
全部的事务依次逐个执行,这样事务之间就彻底不可能产生干扰,也就是说,该级别能够防止脏读、不可重复读以及幻读。可是这将严重影响程序的性能。一般状况下也不会用到该级别。
timeout
:事务的超时时间,默认值为 -1。若是超过该时间限制但事务尚未完成,则自动回滚事务。
readOnly
:指定事务是否为只读事务,默认值为 false;为了忽略那些不须要事务的方法,好比读取数据,能够设置 read-only 为 true。
rollbackFor
:用于指定可以触发事务回滚的异常类型,能够指定多个异常类型。
noRollbackFor
:抛出指定的异常类型,不回滚事务,也能够指定多个异常类型。
面试官就直接问我有没有用过@Transactional,我确定不能说没用过啊,十分自信的说,经常使用。
面试官又问我,在实际开发过程有没有遇到过@Transactional失效的状况,我确定不能说没有啊,再次十分自信的说到,常常。
面试官一脸问号,常常???那你给我说说@Transactional在何时会失效呢?
下面的内容是我将我面试时说的失效场景整理了一下。
若是Transactional
注解应用在非public
修饰的方法上,Transactional将会失效。
之因此会失效是由于在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行先后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute`方法,获取Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Methodmethod, Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() &&!Modifier.isPublic(method.getModifiers())) { return null; }复制代码
Modifier.isPublic会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
注意:protected
、private
修饰的方法上使用 @Transactional
注解,虽然事务无效,但不会有任何报错,这是咱们很容犯错的一点。
数据库引擎要支持事务,若是是MySQL,注意表要使用支持事务的引擎,好比innodb,若是是myisam,事务是不起做用的。
在上面解读propagation 属性的时候,咱们知道
TransactionDefinition.PROPAGATION_SUPPORTS
若是当前存在事务,则加入该事务;若是当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,若是当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,若是当前存在事务,则抛出异常。
当咱们将propagation 属性设置为上述三种时,@Transactional 注解就不会产生效果
上述咱们解读rollbackFor 属性的时候咱们知道
rollbackFor
能够指定可以触发事务回滚的异常类型。
Spring默认抛出了未检查unchecked
异常(继承自 RuntimeException
的异常)或者 Error
才回滚事务;
其余异常不会触发回滚事务。若是在事务中抛出其余类型的异常,但却指望 Spring 可以回滚事务,就须要指定 rollbackFor属性。
// 但愿自定义的异常能够进行回滚 @Transactional(propagation= Propagation.REQUIRED,rollbackFor=MyException.class复制代码
若在目标方法中抛出的异常是 rollbackFor
指定的异常的子类,事务一样会回滚。Spring源码以下:
private int getDepth(Class<?> exceptionClass, int depth) { if (exceptionClass.getName().contains(this.exceptionName)) { // Found it! return depth; } // If we've gone as far as we can go and haven't found it... if (exceptionClass == Throwable.class) { return -1; } return getDepth(exceptionClass.getSuperclass(), depth + 1); }复制代码
咱们来看下面的场景:
好比有一个类User,它的一个方法A,A再调用本类的方法B(不论方法B是用public仍是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A以后,方法B的事务是不会起做用的。这也是常常犯错误的一个地方。
那为啥会出现这种状况?其实这仍是因为使用Spring AOP
代理形成的,由于只有当事务方法被当前类之外的代码调用时,才会由Spring
生成的代理对象来管理。
//@Transactional @GetMapping("/user") private Integer A() throws Exception { User user = new User(); user.setName("javaHuang"); /** * B 插入字段为 topJavaer的数据 */ this.insertB(); /** * A 插入字段为 2的数据 */ int insert = userMapper.insert(user); return insert; } @Transactional() public Integer insertB() throws Exception { User user = new User(); user.setName("topJavaer"); return userMapper.insert(user); }复制代码
这种状况是最多见的一种@Transactional注解失效场景,
@Transactional private Integer A() throws Exception { int insert = 0; try { User user = new User(); user.setCityName("javaHuang"); user.setUserId(1); /** * A 插入字段为 javaHuang的数据 */ insert = userMapper.insert(user); /** * B 插入字段为 topJavaer的数据 */ b.insertB(); } catch (Exception e) { e.printStackTrace(); } }复制代码
若是B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务就不能正常回滚,而是会报出异常
org.springframework.transaction.UnexpectedRollbackException: Transactionrolled back because it has been marked as rollback-only复制代码
解决方法:
第一声明事务的时候加上rollback='exception'
第二 cath代码块里面手动回滚
@Transactional 注解咱们常用,可是每每咱们也只是知道它是一个事务注解,不少时候遇到事务注解失效的状况下,咱们都是一头雾水,看不出个因此然来,花费了很长的时间都不能解决。
经过本文了解了@Transactional 注解的失效场景,在之后遇到这种状况时,基本就能一眼看破,而后摸摸本身光滑的脑门,soga,so easy!
妈妈不再用担忧我找不到本身写的bug了。