当前位置:首页 > Java技术 > SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三)

SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三)

2022年09月16日 10:02:39Java技术6

事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。注释配置是目前流行的使用方式,因此本文将着重介绍基于@Transactional 注解的事务管理。

需要明确几点:

  • 默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。参考这里

  • @Transactional 注解只能应用到 public 方法才有效。参考这里 Method visibility and @Transactional

以下的示例使用的是 mybatis,所以 spring boot 会自动配置一个 DataSourceTransactionManager,我们只需在 **方法(或者类)**加上 @Transactional 注解,就自动纳入 Spring 的事务管理了。

简单的使用方法

只需在方法加上 **@Transactional ** 注解就可以了。

如下有一个保存用户的方法,加入 @Transactional 注解,使用默认配置,抛出异常之后,事务会自动回滚,数据不会插入到数据库。

@Transactional
@Override
public void save() {
    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

我们可以从日志里面看出这些信息
SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三) _ JavaClub全栈架构师技术笔记

@Transactional 注解的属性介绍

下面分别介绍一下 @Transactional 的几个属性。

value 和 transactionManager 属性

它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。

propagation 属性

事务的传播行为,默认值为 Propagation.REQUIRED。

可选的值有:

  • Propagation.REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

  • Propagation.SUPPORTS
    如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • Propagation.MANDATORY 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

  • Propagation.REQUIRES_NEW
    重新创建一个新的事务,如果当前存在事务,暂停当前的事务。

  • Propagation.NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务。

  • Propagation.NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED 和 Propagation.REQUIRED 效果一样。

这些概念理解起来实在是有点儿抽象,后文会用代码示例解释说明。

isolation 属性

事务的隔离级别,默认值为 Isolation.DEFAULT。

可选的值有:

Isolation.DEFAULT

使用底层数据库默认的隔离级别。

  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE

timeout 属性

事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor 属性

抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。


@Transactional 的 propagation 属性代码示例

比如如下代码,save 方法首先调用了 method1 方法,然后抛出了异常,就会导致事务回滚,如下两条数据都不会插入数据库。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

public void method1() {
    User user = new User("宫本武藏");
    userMapper.insertSelective(user);
}

现在有需求如下,就算 save 方法的后面抛异常了,也不能影响 method1 方法的数据插入。或许很多人的想法如下,给 method1 页加入一个新的事务,这样 method1 就会在这个新的事务中执行,原来的事务不会影响到新的事务。比如 method1 方法上面再加入注解 @Transactional,设置 propagation 属性为 Propagation.REQUIRES_NEW,代码如下。

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
    User user = new User("宫本武藏");
    userMapper.insertSelective(user);
}

运行之后,发现然并卵,数据也是没有插入数据库。怎么肥四,看起来很不科学。我们先来看看日志内容。
SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三) _ JavaClub全栈架构师技术笔记

从日志内容可以看出,其实两个方法都是处于同一个事务中,method1 方法并没有创建一个新的事务。

这就得看看 Spring 官方文档了。

SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三) _ JavaClub全栈架构师技术笔记

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

大概意思:在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截,就像上面的 save 方法直接调用了同一个类中的 method1方法,method1 方法不会被 Spring 的事务拦截器拦截。可以使用 AspectJ 取代 Spring AOP 代理来解决这个问题,但是这里暂不讨论。

为了解决这个问题,我们可以新建一个类。

@Service
public class OtherServiceImpl implements OtherService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void method1() {
        User user = new User("风魔小太郎");
        userMapper.insertSelective(user);
    }
}

然后在 save 方法中调用 otherService.method1 方法

@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

这下,otherService.method1 方法的数据插入成功,save 方法的数据未插入,事务回滚。

继续看一下日志内容
SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三) _ JavaClub全栈架构师技术笔记
从日志可以看出,首先创建了 save 方法的事务,由于 otherService.method1 方法的 @Transactional 的 propagation 属性为 Propagation.REQUIRES_NEW ,所以接着暂停了 save 方法的事务,重新创建了 otherService.method1 方法的事务,接着 otherService.method1 方法的事务提交,接着 save 方法的事务回滚。这就印证了只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。

还有几个示例如下。

接着把 save 方法的 @Transactional 注解去掉,otherService.method1 的 @Transactional 注解保持不变,从日志就可以看出,只会创建一个 otherService.method1 方法的事务,两条数据都会插入。

@Autowired
private OtherService otherService;

//    @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();

    User user = new User("服部半藏");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

接着把 save 方法的 @Transactional 注解去掉,save 方法改为调用内部的 method1 方法,从日志就可以看出,完全没有创建任何事务,两条数据都会插入。

// @Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

method1();

User user = new User("服部半藏");
userMapper.insertSelective(user);

if (true) {
    throw new RuntimeException("save 抛异常了");
}

}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
User user = new User(“宫本武藏”);
userMapper.insertSelective(user);
}

这样,其他的几个 propagation 属性值也就比较好理解了。


注解方式的事务使用注意事项

正确的设置@Transactional 的 rollbackFor 属性

默认情况下,如果在事务中抛出了**未检查异常(继承自 RuntimeException 的异常)**或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。

如果在事务中抛出其他类型的异常,并期望 Spring 能够回滚事务,可以指定** rollbackFor**。例:

@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)

通过分析 Spring 源码可以知道,若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

清单 3. RollbackRuleAttribute 的 getDepth 方法

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);
}

@Transactional 只能应用到 public 方法才有效

只有@Transactional 注解应用到 public 方法,才能进行事务管理。这是因为在使用 Spring AOP 代理时,Spring 在调用在图 1 中的 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取表 1. @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

清单 4. AbstractFallbackTransactionAttributeSource

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;}

这个方法会检查目标方法的修饰符是不是 public,若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。

SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三) _ JavaClub全栈架构师技术笔记

Spring 的注解方式的事务实现机制

在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器(图 2 有相关介绍)AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务, 如图 1 所示。

SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三) _ JavaClub全栈架构师技术笔记
图 1. Spring 事务实现机制

Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,图 1 是以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。

正如上文提到的,事务管理的框架是由抽象事务管理器 AbstractPlatformTransactionManager 来提供的,而具体的底层事务处理实现,由 PlatformTransactionManager 的具体实现类来实现,如事务管理器 DataSourceTransactionManager。不同的事务管理器管理不同的数据资源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。

PlatformTransactionManager,AbstractPlatformTransactionManager 及具体实现类关系如图 2 所示。
SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三) _ JavaClub全栈架构师技术笔记
图 2. TransactionManager 类结构

注:

检查异常和非检查异常

那什么是unchecked,什么是checked呢?
通俗的说,编译器能检测到的是checked,就是写代码的时候,idea,eclipse这样的工具会报错的,就是检查异常,检测不到的就是unchecked。
派生于Error或者RuntimeException(比如空指针,1/0)的异常称为unchecked异常。
继承自Exception得异常统称为checked异常,如IOException、TimeoutException等。

只读事务:

@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
只读标志只在事务启动时应用,否则即使配置也会被忽略。
启动事务会增加线程开销,数据库因共享读取而锁定(具体跟数据库类型和事务隔离级别有关)。通常情况下,仅是读取数据时,不必设置只读事务而增加额外的系统开销。

参考资料:

作者:ZerahMu
来源链接:https://blog.csdn.net/NathanniuBee/article/details/90479431

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/41821.html

分享给朋友:

“SpringBoot透彻掌握@Transactional注解 -SpringBoot从入门到熟悉(十三)” 的相关文章

Spring Boot 中使用 @Transactional 注解配置事务管理

Spring Boot 中使用 @Transactional 注解配置事务管理

  可选的值有: 1. Propagation.REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。 2. Propagation.SUPPORTS 如果当前存在事务,则加入该事务;如果...

SpringBoot 常用注解汇总

目录 Spring Boot 常用注解汇总 一、启动注解 @SpringBootApplication @SpringBootConfiguration 注解,继承@Configuration注解,主要用于加载配置文件 @Enab...

【SpringBoot】SpringBoot常用注解

                                       ...

SpringBoot—@ComponentScan注解过滤排除某个类

SpringBoot—@ComponentScan注解过滤排除某个类

关注微信公众号:CodingTechWork,一起学习进步。 问题   在抽取公共swagger配置类时,将swagger放入com.test.common.config包内,其他模块通过@ComponentScan进行进行引用,但有的模块...

SpringBoot jpa事务注解详解

SpringBoot jpa事务注解详解

@Transactional spring 事务注解 1.简单开启事务管理 @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven /> 2.事务注解详...

springBoot service 事务注解@Transactional不起作用的解决

在springBoot使用事物时,发现事务并没有正常执行,没有进行回滚 @Transactional public void add(String companyName,String name) throws MyException{ compan...

springboot  注解竟然不起作用了???

springboot 注解竟然不起作用了???

前段时间整合框架,使用到模板父类,子类中使用事务注解竟然不起作用了,当时一脸的懵逼。。。。代码如下: 此接口是所有业务的模板接口controller入口,如下 控制层调用seivice实现业务层如下:其中有事务注解,但是失效?????,这是为什么呢?...

SpringBoot常用注解介绍及简单使用

一、注解列表如下 @SpringBootApplication:包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解。其中 @ComponentScan:让spring Boot扫描到Configura...

SpringBoot注解最全详解(整合超详细版本)

使用注解的优势:      1.采用纯java代码,不在需要配置繁杂的xml文件      2.在配置中也可享受面向对象带来的好处     ...

springboot+mongodb @Transactional事务注解失效问题处理

问题场景 在一个@service方法上加上 @Transactional(rollbackFor = Exception.class)注解后,发现该方法中调用的另一个方法中抛出异常,在调用该方法之前的对表的修改未回滚,生效了。 问题代码:...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。