1问:@Transactional注解可以应用在什么地方?作用于类:当在类上释放@Transactional注解时,表示该类的所有public方法都配置了相同的事务属性信息。需要注意的是只有public方法才会生效。这是因为AOP的特性。方法:当类配置了@Transactional并且方法也配置了@Transactional时,会优先使用方法上的@Transactional。这个优先级更高。当然我们一般建议放在方法上,不建议配置在类上。对于接口:不推荐这种使用方式,因为一旦在Interface上标注,SpringAOP配置使用CGLib动态代理,会导致@Transactional注解失效。当使用多个数据源时,属性rollbackFor也是我们最常使用的属性。我们可以定义一个或多个异常类,这些异常类必须是Throwable的子类,表示哪些异常类型必须导致事务回滚。默认情况下,事务将在RuntimeException和Error上回滚,但在CheckedException上不会回滚//指定单个异常类@Transactional(rollbackFor=RuntimeException.class)//指定多个异常类@Transactional(rollbackFor={RuntimeException.class,Exception.class})rollbackForClassName这个属性其实和rollbackFor类似。可以定义0个或多个异常名称(对于必须是Throwable子类的异常),并指明哪些异常类型必须导致事务回滚。可以是完全限定类名的子字符串,但目前不支持通配符。例如,ServletException的值将匹配javax.servlet.ServletException及其子类。滚动规则的首选方法noRollbackForClassName此属性与noRollbackFor具有相同的效果。如果不展开,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相同的效果。timeout这个属性是设置这个事务的超时时间(以秒为单位)。默认为底层事务系统的默认超时。设计用于与Propagation.REQUIRED或Propagation.REQUIRES_NEW一起使用,因为它只适用于新启动的事务readOnly布尔标志,如果事务实际上是只读的,可以设置为true,允许在运行时进行相应的优化,默认是false如果一次只执行一条查询语句,则不需要开启事务支持,数据库默认支持SQL执行时的读一致性;如果一次执行多条查询语句,比如统计查询,报表查询,在这种场景下,多条查询SQL要保证整体的读一致性。否则,在上一次SQL查询之后和第二次SQL查询之前,数据被其他用户更改了,整体统计查询会出现读取数据不一致的情况。Status,此时,应该启用事务支持isolation设置事务隔离级别,默认是Isolation。DEFAULT是设计用于与Propagation.REQUIRED或Propagation.REQUIRES_NEW一起使用的,因为它只适用于新启动的事务。如果希望隔离级别语句在参与不同隔离级别的现有事务时被拒绝,请考虑将事务管理器上的“validateExistingTransactions”标志切换为“true”其他属性信息如下(其实就是同MySQL的事务隔离级别,不展开描述):Isolation.DEFAULT:使用底层数据存储的默认隔离级别,MySQL默认的隔离级别是repeatablereadREAD_UNCOMMITTED:读不能提交;READ_COMMITTED:读取提交;REPEATABLE_READS:可重复读;ERIALIZABLE:SerialQuestion3:@Transactional失效注解什么时候适用于非公共方法?事务拦截器TransactionInterceptor会拦截当前前后的事务,会调用DynamicAdvisedInterceptor(CglibAopProxy的内部类)的拦截方法或者JdkDynamicAopProxy`的invoke方法,其实就是调用AbstractFallbackTransactionAttributeSourc`的computeTransactionAttribute方法来获取Transactional注解的事务配置信息。我们可以看到第153-155行if(allowPublicMethodsOnly()&&!Modifier.isPublic(method.getModifiers())){returnnull;}如果不是public,则直接返回null,交易不生效。Tips1:在protected和privat修饰的方法上使用@Transactional注解。public,但是如果被私有方法调用,@Transactional注解也会失败,抛出不符合rollbackFor的异常类型。当你没有指定rollbackFor的回滚异常类型时,如果你抛出一个Exception类型的异常,那么事务无法在Service中的类中互相回滚调用在Controller中添加两个方法,在Controller中调用a方法,在a方法中调用b方法,b方法抛出异常,事务不回滚@Overridepublicvoida(){this.b();}@Transactionalprivatevoidb(){//做一些事情thrownewRuntimeException();}为什么这个方法会导致交易失败呢?也是因为AOP,我们在这个类的方法上打上@Service注解,表示这个类由Spring管理,Bean注册到Spring容器中。Spring通过动态代理的方式实现AOP,这个很容易理解,即容器中的Bean对象其实就是代理对象。Spring也以这种方式支持@Transactional。Spring会将原始对象中的方法进行封装(即勾选这个注解标记的方法时,会为其添加事务)所以当我们通过Controller调用a方法时,不会有事务控制。多线程调用事务方法@Override@Transactionalpublicvoida(){//1-dosomething//2-多线程调用bnewThread(()->b()).start();这个.b();}@Transactionalpublicvoidb(){inta=1/0;从上面的demo我们可以看到,在事务方法a中,调用了事务方法b,但是事务方法b是在另一个线程中调用的,这会导致两个方法不在同一个线程中,而得到的数据库连接不同,所以是两个不同的Transaction,此时b方法抛出异常,a方法无法回滚Q.@Transactional什么时候开始一个事务?答案隐藏在源代码中。我们可以自由调试带有@Transactional注解的文件。g然后观察方法调用栈,发现有这么一个地方观察到了287到295行if(txAttr==null||!(tminstanceofCallbackPreferringPlatformTransactionManager)){//StandardtransactiondemarcationwithgetTransactionandcommit/rollbackcalls.TransactionInfotxInfo=createTransactionIfNecessary(tm,txAttr,joinpointIdentification);对象retVal=null;try{//这是一个绕过建议:调用链中的下一个拦截器。//这通常会导致调用目标对象。//评论说:这是一个环绕执行器,它将调用链中的下一个拦截器,这通常会导致调用目标对象。//简单来说,这个方法会调用你的业务代码retVal=invocation.proceedWithInvocation();仔细看看createTransactionIfNecessary方法的用途。调试进去看看。往下看org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin发现这只是事务的一些准备动作,观察278-284行if(con.getAutoCommit()){txObject.setMustRestoreAutoCommit(true);if(logger.isDebugEnabled()){logger.debug("将JDBC连接["+con+"]切换为手动提交");}con.setAutoCommit(false);}关闭autocommit,开启事务管理,那么开启事务的时间是什么时候?经过调试测试,直接得出该事务是执行准备动作后第一条操作表的语句,真正启动了事务。5Q:你平时是用@Transactional来控制事务吗?虽然@Transactional可以在不侵入的情况下为我们的代码添加事务控制,但是确实有很多需要注意事务失败的地方,而且在代码逻辑中,@Transactional容易让事务控制粒度过大,而@Transactional是基于关于AOP的实现,但是在代码中,有时候我们的切面很多,不同的切面可能处理不同的事情,多个切面之间可能会相互影响。可以考虑手动控制事务的开启和关闭@ResourceprotectedTransactionTemplatetransactionTemplate;transactionTemplate.execute(input->{})@Transactional不是不能用,而是需要根据情况调整。在规范中添加关于@Transactional的注释
