当前位置:   article > 正文

@Transational 事务详解_@transactional

@transactional

1.1 @Transactional介绍

1、@Transactional注解 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

2、虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional注解应该只被应用到 public 方法上,这是由Spring AOP的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

3、需要注意的是,在同一个类中调用被 @Transactional 注解修饰的方法时,事务处理可能会失效,因为 Spring 会在运行时为该类创建一个代理对象,而在同一个类中调用方法时,是不会经过代理对象的。如果需要在同一个类中调用被 @Transactional 注解修饰的方法,可以将该方法抽取到另一个类中,并通过依赖注入的方式调用。也可以先获取到本类的代理对象再调用内部方法。

4、在 Spring 中,事务代理默认使用 JDK 动态代理,这意味着只能代理实现了接口的类。如果需要代理没有实现接口的类,可以使用 CGLIB 代理。

1.2 @Transactional注解属性

@Transactional注解里面的各个属性和咱们在上面讲的事务属性里面是一一对应的。用来设置事务的传播行为、隔离规则、回滚规则、事务超时、是否只读。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
 
    /**
     * 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
     */
    @AliasFor("transactionManager")
    String value() default "";
 
    /**
     * 同上。
     */
    @AliasFor("value")
    String transactionManager() default "";
 
    /**
     * 事务的传播行为,默认值为 REQUIRED。
     */
    Propagation propagation() default Propagation.REQUIRED;
 
    /**
     * 事务的隔离规则,默认值采用 DEFAULT。
     */
    Isolation isolation() default Isolation.DEFAULT;
 
    /**
     * 事务超时时间。
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
 
    /**
     * 是否只读事务
     */
    boolean readOnly() default false;
 
    /**
     * 用于指定能够触发事务回滚的异常类型。
     */
    Class<? extends Throwable>[] rollbackFor() default {};
 
    /**
     * 同上,指定类名。
     */
    String[] rollbackForClassName() default {};
 
    /**
     * 用于指定不会触发事务回滚的异常类型
     */
    Class<? extends Throwable>[] noRollbackFor() default {};
 
    /**
     * 同上,指定类名
     */
    String[] noRollbackForClassName() default {};
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

1.2.1 value、transactionManager属性

它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。大多数项目只需要一个事务管理器。然而,有些项目为了提高效率、或者有多个完全不同又不相干的数据源,从而使用了多个事务管理器。机智的Spring的Transactional管理已经考虑到了这一点,首先定义多个transactional manager,并为qualifier属性指定不同的值;然后在需要使用@Transactional注解的时候指定TransactionManager的qualifier属性值或者直接使用bean名称。配置和代码使用的例子:

<tx:annotation-driven/>
 
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource1"></property>
    <qualifier value="datasource1Tx"/>
</bean>
 
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource2"></property>
    <qualifier value="datasource2Tx"/>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public class TransactionalService {
 
    @Transactional("datasource1Tx")
    public void setSomethingInDatasource1() { ... }
 
    @Transactional("datasource2Tx")
    public void doSomethingInDatasource2() { ... }
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

1.2.2 propagation属性

propagation用于指定事务的传播行为,默认值为 REQUIRED。propagation有七种类型,就是我们在上文中讲到的事务属性传播行为的七种方式,如下所示:

propagation属性事务属性-传播行为含义
REQUIREDTransactionDefinition.PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。
SUPPORTSTransactionDefinition.PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORYTransactionDefinition.PROPAGATION_MANDATORY表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
REQUIRES_NEWTransactionDefinition.PROPAGATION_REQUIRES_NEW表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。
NOT_SUPPORTEDTransactionDefinition.PROPAGATION_NOT_SUPPORTED表示该方法不应该运行在事务中。如果当前存在事务,就把当前事务挂起。
NEVERTransactionDefinition.PROPAGATION_NEVER表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
NESTEDTransactionDefinition.PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。在嵌套事务中,内层事务的提交和回滚不会影响外层事务的状态,但是内层事务的提交需要等到外层事务提交后才会生效。如果外层事务回滚,则所有嵌套的事务也会回滚。

需要注意的是,嵌套事务需要数据库支持,并且不是所有数据库都支持嵌套事务。在使用嵌套事务时,需要特别小心,避免出现死锁和数据不一致等问题。|

1.2.3 isolation属性

isolation用于指定事务的隔离规则,默认值为DEFAULT。@Transactional的隔离规则和上文事务属性里面的隔离规则也是一一对应的。总共五种隔离规则,如下所示:

@isolation属性事务属性-隔离规则含义脏读不可重复读幻读
DEFAULTTransactionDefinition.ISOLATION_DEFAULT使用后端数据库默认的隔离级别
READ_UNCOMMITTEDTransactionDefinition.ISOLATION_READ_UNCOMMITTED允许读取尚未提交的数据变更(最低的隔离级别)
READ_COMMITTEDTransactionDefinition.ISOLATION_READ_COMMITTED允许读取并发事务已经提交的数据
REPEATABLE_READTransactionDefinition.ISOLATION_REPEATABLE_READ对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
SERIALIZABLETransactionDefinition.ISOLATION_SERIALIZABLE最高的隔离级别,完全服从ACID的隔离级别,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

1.2.4 timeout

timeout用于设置事务的超时属性。

1.2.5 readOnly

readOnly用于设置事务是否只读属性。

1.2.6 rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

rollbackFor、rollbackForClassName用于设置那些异常需要回滚;noRollbackFor、noRollbackForClassName用于设置那些异常不需要回滚。他们就是在设置事务的回滚规则。

1.3 @Transactional注解的使用

@Transactional注解的使用关键点在理解@Transactional注解里面各个参数的含义。这个咱们在上面已经对@Transactional注解参数的各个含义做了一个简单的介绍。接下来,咱们着重讲一讲@Transactional注解使用过程中一些注意的点。

@Transactional注解内部实现依赖于Spring AOP编程。而AOP在默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为。

1.3.1 @Transactional 注解尽量直接加在方法上

为什么:因为@Transactional直接加在类或者接口上,@Transactional注解会对类或者接口里面所有的public方法都有效(相当于所有的public方法都加上了@Transactional注解,而且注解带的参数都是一样的)。第一影响性能,可能有些方法我不需要@Transactional注解,第二方法不同可能@Transactional注解需要配置的参数也不同,比如有一个方法只是做查询操作,那咱们可能需要配置Transactional注解的readOnly参数。所以强烈建议@Transactional注解直接添加的需要的方法上。

1.3.2 @Transactional 注解必须添加在public方法上,private、protected方法上是无效的

在使用@Transactional 的时候一定要记住,在private,protected方法上添加@Transactional 注解不会有任何效果。相当于没加一样。即使外部能调到protected的方法也无效。和没有添加@Transactional一样。

1.3.3 函数之间相互调用

关于有@Transactional的函数之间调用,会产生什么情况。这里咱们通过几个例子来说明。

1.3.3.1 同一个类中函数相互调用

同一个类AClass中,有两个函数aFunction、aInnerFunction。aFunction调用aInnerFunction。而且aFunction函数会被外部调用。

情况0: aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常。

public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
结果:两个函数操作的数据都会回滚。
  • 1

情况1:两个函数都添加了@Transactional注解。aInnerFunction抛异常。

public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
结果:同第一种情况一样,两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效。@Transactional注解只有外部调用才有效。
  • 1

情况2: aFunction不添加注解,aInnerFunction添加注解。aInnerFunction抛异常。

public class AClass {
 
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    @Transactional(rollbackFor = Exception.class)
    protected void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
结果:两个函数对数据库的操作都不会回滚。因为内部函数@Transactional注解添加和没添加一样。
  • 1

情况3:aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常,不过在aFunction里面把异常抓出来了。

public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
结果:两个函数里面的数据库操作都成功。事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。(当然了要看你添加的@Transactional注解有没有效)。
  • 1

1.3.3.1. 不同类中函数相互调用

两个类AClass、BClass。AClass类有aFunction、BClass类有bFunction。AClass类aFunction调用BClass类bFunction。最终会在外部调用AClass类的aFunction。

情况0:aFunction添加注解,bFunction不添加注解。bFunction抛异常。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        bClass.bFunction();
    }
 
}

@Service()
public class BClass {
 
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
结果:两个函数对数据库的操作都回滚了。
  • 1

情况1:aFunction、bFunction两个函数都添加注解,bFunction抛异常。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        bClass.bFunction();
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
结果:两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。这种情况下,你可以认为事务rollback了两次。两个函数都有异常。
  • 1

情况2:aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

结果:两个函数数据库操作都没成功。而且还抛异常了。org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。看打印出来的解释也很好理解把。咱们也可以这么理解,两个函数用的是同一个事务。bFunction函数抛了异常,调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行,aFunction函数里面把异常给抓出来了,这个时候aFunction函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了,不让调了。

情况3:aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。这里要注意bFunction函数@Transactional注解我们是有变化的,加了一个参数propagation = Propagation.REQUIRES_NEW,控制事务的传播行为。表明是一个新的事务。其实咱们情况3就是来解决情况2的问题的。

@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

结果:bFunction函数里面的操作回滚了,aFunction里面的操作成功了。有了前面情况2的理解。这种情况也很好解释。两个函数不是同一个事务了。

1.3.4 同类内方法调用如何使事务生效

上面示例中,在同一个类中调用被 @Transactional 注解修饰的方法时,事务处理可能会失效,因为 Spring 会在运行时为该类创建一个代理对象,而在同一个类中调用方法时,是不会经过代理对象的。如果需要在同一个类中调用被 @Transactional 注解修饰的方法,可以将该方法抽取到另一个类中,并通过依赖注入的方式调用。也可以先获取到本类的代理对象再调用内部方法。

方法1:使用 AopContext 类获取代理对象

需要注意的是,使用 AopContext 类获取代理对象可能会带来安全风险,因此默认情况下是禁用的。如果需要启用该功能,可以在配置文件中设置 expose-proxy 属性为 true。例如,在 Spring Boot 中可以通过以下方式设置:spring.aop.proxy-target-class: true

@Service
public class UserService {

   @Autowired
   private UserDao userDao;

   @Transactional(propagation = Propagation.REQUIRED)
   public void updateUser(User user) {
       // 获取当前对象的代理对象
       UserService userService = (UserService) AopContext.currentProxy();

       // 调用内部方法,启用事务处理
       userService.updateUserDetail(user.getUserId(), user.getUserDetail());
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void updateUserDetail(Long userId, UserDetail userDetail) {
       userDao.updateUserDetail(userId, userDetail);
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

方法2:使用 beanFactory.getBean 类获取代理对象

@Service
public class UserService {

   @Autowired
   private BeanFactory beanFactory;

   @Autowired
   private UserDao userDao;

   @Transactional(propagation = Propagation.REQUIRED)
   public void updateUser(User user) {
       // 获取当前对象的代理对象
       UserService userService = beanFactory.getBean(UserService.class);

       // 调用内部方法,启用事务处理
       userService.updateUserDetail(user.getUserId(), user.getUserDetail());
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void updateUserDetail(Long userId, UserDetail userDetail) {
       userDao.updateUserDetail(userId, userDetail);
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在上面的代码中,updateUser() 方法通过 beanFactory.getBean(UserService.class) 方法获取当前对象的代理对象,然后调用内部方法 updateUserDetail(),从而启用事务处理。需要注意的是,在使用 getBean() 方法获取当前对象的代理对象时,要求该类必须是一个 Spring 管理的 Bean,否则会抛出异常。

总结:
要知道@Transactional注解里面每个属性的含义。@Transactional注解属性就是来控制事务属性的。通过这些属性来生成事务。
要明确我们添加的@Transactional注解会不会起作用。@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,要切记。这是由AOP的特性决定的。
要明确事务的作用范围,有@Transactional的函数调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,还是沿用之前的事务。稍不注意就会抛UnexpectedRollbackException异常。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/225475
推荐阅读
相关标签
  

闽ICP备14008679号