如何正确打开 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 中可以配置的属性如下。

属性名默认值含义
name方法名,可以使用通配符,遵循 ant 风格;必填
propagationREQUIRED事务传播行为;可取值包括 REQUIRED | SUPPORTS | MANDATORY | REQUIRES_NEW | NOT_SUPPORTED | NEVER | NESTED
isolationDEFAULT事务隔离级别;可取值包括 DEFAULT | READ_UNCOMMITTED | READ_COMMITTED | REPEATABLE_READ | SERIALIZABLE
timeout-1事务超时时间,单位:秒
read-onlyfalse是否为只读事务
rollback-for异常类;遇到该异常回滚事务;默认 RuntimeException 或 Error 异常会回滚事务
no-rollback-for异常类;遇到该异常时不要回滚事务

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

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

注解声明式事务

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

  1. Spring XML 配置文件中配置 <tx:annotation-driven/>
  2. 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 做了两处改造。

  1. 第一处在类上添加了 @Transactional 注解,默认方法执行时遇到 Exception 异常时回滚事务。
  2. 第二处在 getOne 方法上添加了 @Transactional 注解,并且设置了 rollbackFor 为 true,表示这是一个只读事务。

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

  • 子类方法上的 @Transactional
  • 父类方法上的 @Transactionoal
  • 子类上的 @Transactional
  • 父类上的 @Transactional

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

属性名默认值含义
value事务管理器的 bean 名称;transactionManager 的别名
transactionManager事务管理器的 bean 名称;value 的别名
propagationPropagation.REQUIRED事务传播行为;可取值与 xml 配置相似,参见 Propagation 类
isolationIsolation.DEFAULT事务隔离级别;可取值与 xml 配置相似,参见 Isolation
timeout-1事务超时时间,单位:秒
readOnlyfalse是否为只读事务
rollbackFor导致回滚的异常 Class 数组
rollbackForClassName导致回滚的异常类名称数组
noRollbackFor不会导致回滚的异常 Class 数组
noRollbackForClassName不会导致回滚的异常类名称数组

编程式事务

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

PlatformTransactionManager

前面在 XML 的配置中,我们配置了一个 DataSourceTransactionManager,这是 PlatformTransactionManager 的子类,在 springframework 5.2 版本之前,PlatformTransactionManager 是事务管理的核心接口,我们也主要使用这个接口以编程的方式使用 Spring 事务,5.2 版本时又提出了一个 ReactiveTransactionManager 接口。PlatformTransactionManager 相关类图如下。
PlatformTransactionManager 类图可以看到 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 事务的实现将会更加轻松。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. Spring事务是一种用于管理数据库操作的机制,它可以保证一系列数据库操作要么全部成功提交,要么全部回滚。Spring事务通过将一组数据库操作封装在一个事务中,确保数据的一致性和完整性。 2. Spring事务传播是指在多个事务方法相互调用的情况下,事务应该如何传播和管理。Spring定义了四种事务传播行为: - REQUIRED:如果当前存在一个事务,则加入该事务;如果不存在事务,则创建一个新事务。 - SUPPORTS:如果当前存在一个事务,则加入该事务;如果不存在事务,则以非事务方式执行。 - MANDATORY:如果当前存在一个事务,则加入该事务;如果不存在事务,则抛出异常。 - REQUIRES_NEW:创建一个新事务,并挂起当前事务(如果存在)。 3. Spring事务的传播特性包括: - REQUIRED:默认的传播特性,如果当前存在一个事务,则加入该事务;如果不存在事务,则创建一个新事务。 - SUPPORTS:如果当前存在一个事务,则加入该事务;如果不存在事务,则以非事务方式执行。 - MANDATORY:如果当前存在一个事务,则加入该事务;如果不存在事务,则抛出异常。 - REQUIRES_NEW:创建一个新事务,并挂起当前事务(如果存在)。 - NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将其挂起。 - NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。 - NESTED:如果当前存在一个事务,则在嵌套事务中执行;如果不存在事务,则创建一个新事务。 4. Spring框架的事务管理器有两个主要功能: - 开启和关闭事务事务管理器负责在事务开始时打开数据库连接,并在事务结束时关闭连接。它确保事务的边界正确地管理了数据库连接的生命周期。 - 提供事务的隔离级别和传播特性控制:事务管理器可以配置事务的隔离级别(如读未提交、读已提交、可重复读、串行化)和传播特性,以满足不同业务场景下的需求。它可以根据需要设置不同的传播行为,保证多个事务方法之间的正确交互和数据一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值