乐趣区

关于java:阿里一面-五问-Transactional

1 问:@Transactional 注解能够作用于哪些地方?

  • 作用于类:当把 @Transactional 注解放在类上时,示意所有该类的 public 办法都配置雷同的事务属性信息,需注意,只有 public 办法才会失效,此处是因为 AOP 的个性起因
  • 作用于办法:当类配置了 @Transactional,办法也配置了 @Transactional,会优先采取办法上的 @Transactional,此优先级较高,当然咱们个别倡议,都是放在办法上,不倡议配置在类上
  • 作用于接口:不举荐这种应用办法,因为一旦标注在 Interface 上并且配置了 Spring AOP 应用 CGLib 动静代理,将会导致 @Transactional 注解生效

2 问:@Transactional 具备哪些属性

value

指定应用的事务管理器,在多数据源的时候施展用途

rollbackFor

这个属性,也是咱们最常常应用到的属性,咱们能够定义一个或多个异样的 classes,它们必须是 Throwable 的子类,批示哪些异样类型必须导致事务回滚。

默认状况下,事务将在 RuntimeException 和 Error 上回滚,但不会在 CheckedException 上回滚

// 指定繁多异样类
@Transactional(rollbackFor=RuntimeException.class)

// 指定多个异样类
@Transactional(rollbackFor={RuntimeException.class, Exception.class})
rollbackForClassName

这个属性其实和 rollbackFor 有着殊途同归之妙,可定义 0 个或多个异样名称(对于必须是 Throwable 子类的异样),要批示哪些异样类型必须导致事务回滚。

能够是齐全限定类名的子字符串,然而目前不反对通配符。

例如,ServletException 的值将匹配 javax.servlet.ServletException 及其子类

noRollbackFor

这个属性需定义 0 个或多个异样 Classes,它们必须是 Throwable 子类,批示哪些异样类型不可导致事务回滚,这是构建回滚规定的首选办法

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.REQUIREDPropagation.REQUIRES_NEW 一起应用而设计,因为它仅实用于新启动的事务

readOnly

如果事务实际上是只读的,则能够将其设置为 true 布尔标记,从而容许在运行时进行相应的优化,默认为 false

如果你一次执行单条查问语句,则没有必要启用事务反对,数据库默认反对 SQL 执行期间的读一致性;

如果你一次执行多条查问语句

例如统计查问,报表查问,在这种场景下,多条查问 SQL 必须保障整体的读一致性,否则,在前条 SQL 查问之后,后条 SQL 查问之前,数据被其余用户扭转,则该次整体的统计查问将会呈现读数据不统一的状态,此时,应该启用事务反对

isolation

设置事务隔离级别,默认为 Isolation.DEFAULT
专为与 Propagation.REQUIREDPropagation.REQUIRES_NEW 一起应用而设计,因为它仅实用于新启动的事务。如果您心愿隔离级别申明在参加具备不同隔离级别的现有事务时被回绝,请思考将事务管理器上的“validateExistingTransactions”标记切换为“true”

其余属性信息如下(其实和 MySQL 的事务隔离级别一样的,不开展阐明):

  • Isolation.DEFAULT:应用底层数据存储的默认隔离级别,MySQL 默认的隔离级别为可反复读

READ_UNCOMMITTED:读可不提交;

READ_COMMITTED:读提交;

REPEATABLE_READS:可反复读;

ERIALIZABLE:串行化

3 问:@Transactional 什么时候生效

  • 注解使用在非 public 的办法上

    事务拦截器 TransactionInterceptor 会在目前前后进行事务拦挡,会对 DynamicAdvisedInterceptor(CglibAopProxy 的外部类)的 intercept 办法或 JdkDynamicAopProxy` 的 invoke 办法 进行调用

    实质其实就是调用AbstractFallbackTransactionAttributeSourc` 的 computeTransactionAttribute 办法,获取 Transactional 注解的事务配置信息

    咱们能够看到 153-155 行

          if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}

如果不是 public,则间接返回 null,事务即不失效

Tips1:protected、privat 润饰的办法上应用 @Transactional 注解,尽管事务有效,但不会有任何报错,这是咱们很容犯错的一点

Tips2:即便你的办法是 public 的,然而如果被 private 的办法调用,@Transactional 注解同样也会生效

  • 抛出不合乎 rollbackFor 的异样类型

    在不指定 rollbackFor 的回滚异样类型的时候,如果你抛出了 Exception 的异样类型,那么事务无奈回滚

  • 在类中互相调用

    在 Service 中增加两个办法,在 Controller 中调用 a 办法,a 办法中调用 b 办法,b 办法抛出异样,后果事务也没进行回滚

  @Override
  public void a (){this.b();
  }
  
  @Transactional
  private void b(){
    // do something
     throw new RuntimeException();}

为什么此种办法会导致事务生效呢?也是因为 AOP 的缘故

咱们在该类的办法上标记了 @Service 注解,示意把这个类交由 Spring 治理,将该 Bean 注册到 Spring 的容器中,Spring 又是通过动静代理模式来实现 AOP,简略了解就是容器中的 Bean 对象实际上都是代理对象

Spring 也正是通过该形式对 @Transactional 进行反对的,Spring 会对原对象中的办法进行封装(即查看到标有该注解的办法时,就会为它加上事务)

所以咱们通过 Controller 对 a 办法进行调用的时候,不会进行事务管制

  • 多线程调用事务办法
    @Override
    @Transactional
    public void a (){
        // 1-do something
        
        // 2- 多线程调用 b
        new Thread(() -> b()).start();
        this.b();}

    @Transactional
    public void b(){int a = 1 / 0 ;}

从下面的 demo 中,咱们能够看到事务办法 a 中,调用了事务办法 b,然而事务办法 b 是在另外一个线程中调用的

这样会导致两个办法不在同一个线程中,获取到的数据库连贯不一样,从而是两个不同的事务,此时 b 办法中抛了异样,a 办法也无奈进行回滚

  • 底层事务设置谬误

    表不反对事务,或者未开启事务

  • 异样未被抛出异样,被 catch 住了
  • 事务的流传的个性设置谬误

4 问、@Transactional 什么时候开启事务

这个答案藏在源码里

咱们随便在一个带有 @Transactional 注解进行 debug

而后察看办法调用栈,发现这么一个中央

察看到 287 行 至 295 行

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
        // 正文写着:这是一个盘绕执行器,会去调用链中的下一个拦截器。这通常会导致指标对象被调用。// 简略来说,就是这个办法会去调用你的业务代码
                retVal = invocation.proceedWithInvocation();}

小眼一看 createTransactionIfNecessary 这个办法是用来干嘛的,debug 进去看看,走上来

重点关注到 org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

发现此处只是针对事务做了一些筹备动作,察看到 278-284 行

            if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);
            }

将主动提交敞开了,启动了事务管理

那么事务真正启动的机会是什么时候呢?

通过 debug 和测试,间接给出论断

事务是是在执行到筹备动作后的第一个操作表的语句,事务才算是真正启动。

5 问:平时管制事务都是应用 @Transactional 吗?

@Transactional 尽管能够不侵入的为咱们的代码加上事务管制,然而确实有许多须要留神事务生效的中央

并且在代码逻辑中,@Transactional 容易使事务管制粒度过大,而且 @Transactional 基于 AOP 实现的,然而在代码中,有时候咱们会有很多切面,不同的切面可能会来解决不同的事件,多个切面之间可能会有相互影响

能够思考本人手动管制事务的开启敞开

 @Resource
 protected TransactionTemplate transactionTemplate;
 
 transactionTemplate.execute(input -> {})

@Transactional 不是不能够用,而是须要因场景制宜

在补充一点标准中对于 @Transactional 的阐明

退出移动版