如何正确打开 Spring 事务?

简介: 前言Spring 事务传播行为是 Spring 中一个常见的面试题,它贯穿于 Spring 的事务管理中,因此想要理解 Spring 事务传播行为,首先要对 Spring 的事务管理有一个整体的认识。本篇先对 Spring 事务的使用加以介绍,后续逐步分析 Spring 事务实现,直至深入理解 Spring 事务传播行为为止。关于事务,如果文章前的你不熟悉的话,可以先参阅《数据库事务基础知识》。

前言


Spring 事务传播行为是 Spring 中一个常见的面试题,它贯穿于 Spring 的事务管理中,因此想要理解 Spring 事务传播行为,首先要对 Spring 的事务管理有一个整体的认识。本篇先对 Spring 事务的使用加以介绍,后续逐步分析 Spring 事务实现,直至深入理解 Spring 事务传播行为为止。关于事务,如果文章前的你不熟悉的话,可以先参阅《数据库事务基础知识》。


背景


Spring 事务基于 Java,而 Java 已经提出了一套 JDBC 规范用于操作数据库,使用 JDBC 的 API,我们可以轻松的提交回滚事务,那么 Spring 为什么又提供一套事务解决方案呢?


由于 JDBC 操作数据库的 API 使用较为繁琐,直接使用 JDBC 会产生大量的样板式代码,各持久层框架(规范)均对 JDBC 进行封装,提供了不同的事务使用方式,包括 JPA、JTA、Hibernate 等等,并且有的框架可能会对应用运行环境产生一定的依赖,为了解决这些问题,Spring 提供了一个 spring-tx 的模块,提供了统一的事务使用方式,并且易于扩展,如 MyBatis 也可以直接使用 Spring 的事务管理。如果你对这些感兴趣,也可以具体参考 Spring 官方文档。


声明式事务


Spring 的事务使用方式分为声明式事务和编程式事务,下面分别从这两块讨论。


声明式事务直接在 XML 或注解中进行配置即可,使用此方式 Spring 对业务代码的侵入性较小。Spring 同时支持使用 XML 和注解进行事务相关配置。


假定我们有如下的业务类,类中的方法均会操作数据库。


public interface IService {
   Object getOne(Integer id);
   int insert(Object obj);
   int update(Object obj);
   int delete(Integer id);
}
@Data
public class ServiceImpl implements IService {
   private JdbcTemplate jdbcTemplate;
   @Override
   public Object getOne(Integer id) {
       return null;
   }
   @Override
   public int insert(Object obj) {
       return 0;
   }
   @Override
   public int update(Object obj) {
       return 0;
   }
   @Override
   public int delete(Integer id) {
       return 0;
   }
}


假定我们想通过 spring-jdbc 模块提供的 JdbcTemplate 来操作数据库,我们在 ServiceImpl 类中引入了 JdbcTemplate 类型的变量,然后在方法中使用 JdbcTemplate 操作数据库即可,无需考虑事务何时创建、提交、回滚。


那么这样就可以了吗?显然没有那么简单,如果仅仅只是使用 JdbcTemplate ,那么使用 JdbcTemplate 操作数据库,每条 SQL 都将是一个事务,达不到我们想要的效果。


XML 声明式事务


先考虑使用 XML 配置 Spring 事务。代码如下。


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       https://www.springframework.org/schema/tx/spring-tx.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="12345678"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="service" class="com.zzuhkp.blog.transaction.ServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" rollback-for="java.lang.Exception"/>
            <tx:method name="get*" rollback-for="java.lang.Exception" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="servicePointcut" expression="execution(* com.zzuhkp.blog.transaction.IService.*(..))"/>
        <aop:advisor pointcut-ref="servicePointcut" advice-ref="transactionAdvice"/>
    </aop:config>
</beans>


Spring 应用上下文 XML 配置中,我们依次配置了 dataSource、jdbcTemplate、service 三个 bean,事实上,有了这三个配置之后,service 的功能已经可以正常执行,但是 JdbcTemplate 每次执行 SQL 都将开启一个事务,不满足我们的需求,因此还需要后面的配置。


在后面的配置中,我们又分别添加了 transactionManager、transactionAdvice、advisor 作为 bean。


我们配置的 transactionManager 类型为 DataSourceTransactionManager,这是 PlatformTranasactionManager 的子类,PlatformTranasactionManager 是 Spring 事务的核心接口,提供了统一的事务管理抽象,不同的环境使用不同的实现即可。在这里需要注意的是一定要保证 transactionManager 和 jdbcTemplate 引用了同一个 dataSource,这样 Spring 事务管理才会生效。


transactionAdvice bean 依赖 transactionManager,可以通过 tx:method 元素节点配置不同的方法的事务行为,如在上面的配置中,我们希望对 get 开头的方法优化为只读事务。tx:method 中可以配置的属性如下。


image.png


transactionAdvice 用于在目标方法前后添加事务相关的行为,而 advisor 将 pointcut、advice 整合到一起,因此后面我们还配置了一个 advisor。


至此事务相关配置结束,此时业务代码对 Spring 的事务管理是无感的,但是在 XML 配置文件中产生了大量有关 AOP、事务相关的配置,这要求用户对 Spring AOP 和 事务管理有一定了解,因此提高了使用 Spring 事务管理的门槛,为了解决这个问题,我们可以通过注解的方式配置事务。


注解声明式事务


通过注解的方式使用 Spring 事务管理,首先需要开启这个功能,有两种方式。


Spring XML 配置文件中配置 <tx:annotation-driven/>。

Spring 配置类上添加 @EnableTransactionManagement 注解。

开启注解后还需要在 Spring 中配置 TransactionManager 作为 bean,通常使用的实现为 DataSourceTransactionManager,如果引入了 spring-boot-starter-jdbc,则无需显式配置 TransactionManager,而只需要配置一个 Datasource 即可。


开启注解支持后需要在 Spring Bean 的类或方法上使用 @Transactional 注解。


将我们 XML 中的配置转换为注解,代码如下。


@Data
@Transactional(rollbackFor = Exception.class)
public class ServiceImpl implements IService {
    private JdbcTemplate jdbcTemplate;
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    @Override
    public Object getOne(Integer id) {
        return null;
    }
    @Override
    public int insert(Object obj) {
        return 0;
    }
    @Override
    public int update(Object obj) {
        return 0;
    }
    @Override
    public int delete(Integer id) {
        return 0;
    }
}


这里只对 ServiceImpl 做了两处改造。


第一处在类上添加了 @Transactional 注解,默认方法执行时遇到 Exception 异常时回滚事务。

第二处在 getOne 方法上添加了 @Transactional 注解,并且设置了 rollbackFor 为 true,表示这是一个只读事务。

可以看到 @Transactional 和 XML 配置中的 tx:method 元素节点的功能类似,都是提供创建事务的元信息。@Transactional 注解可以同时添加到类和方法上,优先级从高到底如下:


子类方法上的 @Transactional

父类方法上的 @Transactionoal

子类上的 @Transactional

父类上的 @Transactional

@Transactional 注解可配置的属性如下。


image.png


编程式事务


编程式事务是指直接在代码中使用 Spring 提供的事务 API 开启事务支持,因此这种方式对代码的侵入性较强,但灵活性相对较高。


PlatformTransactionManager


前面在 XML 的配置中,我们配置了一个 DataSourceTransactionManager,这是 PlatformTransactionManager 的子类,在 springframework 5.2 版本之前,PlatformTransactionManager 是事务管理的核心接口,我们也主要使用这个接口以编程的方式使用 Spring 事务,5.2 版本时又提出了一个 ReactiveTransactionManager 接口。PlatformTransactionManager 相关类图如下。


image.png


可以看到 PlatformTransactionManager 的实现有很多,这个接口将不同的事务管理方式进行了统一,以便可以用相同的方式在 Spring 中使用事务。使用这个类需要先了解它提供的 API,接口定义如下。


public interface PlatformTransactionManager extends TransactionManager {
  // 开启事务
  TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
  // 提交事务
  void commit(TransactionStatus status) throws TransactionException;
  // 回滚事务
  void rollback(TransactionStatus status) throws TransactionException;
}


可以看到 PlatformTransactionManager 将事务的控制权全部交给用户,由用户决定何时开启、提交、回滚事务。可以猜想到,声明式事务的底层也是使用了 PlatformTransactionManager,然后结合 AOP,方法执行前创建事务,方法结束后提交或回滚事务。


PlatformTransactionManager 接口的方法中,我们遇到了两个新的类,一个是 TransactionDefinition,这是开启事务时使用的元数据,XML 配置中的 tx:method 元素节点和注解配置中的 @Transactional 属性都将转换为这个接口,接口的定义如下。


public interface TransactionDefinition {
  // 获取事务传播行为
  default int getPropagationBehavior() {
    return PROPAGATION_REQUIRED;
  }
  // 获取事务隔离级别
  default int getIsolationLevel() {
    return ISOLATION_DEFAULT;
  } 
  // 获取超时时间,单位:秒
  default int getTimeout() {
    return TIMEOUT_DEFAULT;
  }
  // 事务是否只读
  default boolean isReadOnly() {
    return false;
  } 
  // 获取事务名称
  @Nullable
  default String getName() {
    return null;
  }
}


TransactionStatus 主要包含了创建后的事务的一些元信息,经常调用的一个方法是#setRollbackOnly,将事务设置为仅回滚,这样在声明式事务中就不用以抛出异常的方式回滚事务,Spring 在提交事务时进行检查以决定是提交事务还是回滚事务。


PlatformTransactionManager 使用示例如下。


public class ServiceImpl implements IService {
    private JdbcTemplate jdbcTemplate;
    private PlatformTransactionManager platformTransactionManager;
    public ServiceImpl(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
        platformTransactionManager = new DataSourceTransactionManager(dataSource);
    }
    @Override
    public int insert(Object obj) {
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
        try {
          return jdbcTemplate.update("insert into user(id,username,password) values (1,'hkp','123456')");
        } catch (Exception e) {
            platformTransactionManager.rollback(transactionStatus);
            throw e;
        }
        platformTransactionManager.commit(transactionStatus);
        return 0;
    } 
}


TransactionTemplate


在声明式事务中,通过注解的方式,业务代码可以专注于自己的逻辑,而不需要把精力放在事务中,那么在编程式事务中有没有合适的方式简化事务的使用呢?


为了简化编程式事务的使用,Spring 提供了一个 TransactionTemplate 类,这个类对 PlatformTransactionManager 封装,接受一个回调,用户只需要专注于业务代码即可,同时它也继承了 DefaultTransactionDefinition,因此也可以直接设置事务属性。使用示例如下。


public class ServiceImpl implements IService {
    private JdbcTemplate jdbcTemplate;
    private TransactionTemplate transactionTemplate;
    public ServiceImpl(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));
    }
    @Override
    public int insert(Object obj) {
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        int count = transactionTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {
                return jdbcTemplate.update("insert into user(id,username,password) values (1,'hkp','123456')");
            }
        });
        return count;
    }
}


总结

本篇主要对 Spring 声明式事务和编程式事务的使用进行了简单的介绍,先鸟瞰全貌,再深入细节,有了对 Spring 事务的基本认识之后,后面再研究 Spring 事务的实现将会更加轻松。

目录
相关文章
|
2月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
3月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
8月前
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
8月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
191 0
|
8月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1091 0
|
8月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
212 0
|
6月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
330 10
|
5月前
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
432 1
|
8月前
|
SQL Java 数据库连接
Spring中的事务是如何实现的
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮ 常重要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务
|
8月前
|
JavaScript Java 开发者
Spring事务失效,常见的情况有哪些?
本文总结了Spring事务失效的7种常见情况,包括未启用事务管理功能、方法非public类型、数据源未配置事务管理器、自身调用问题、异常类型错误、异常被吞以及业务和事务代码不在同一线程中。同时提供了两种快速定位事务相关Bug的方法:通过查看日志(设置为debug模式)或调试代码(在TransactionInterceptor的invoke方法中设置断点)。文章帮助开发者更好地理解和解决Spring事务中的问题。
315 7