Spring事务管理深度解析:从原理到实践

一、什么是事务?

在软件开发领域,大家对事务的概念想必都不陌生,事务简单来说,就是逻辑上的一组操作,这组操作具有一个显著特点:要么全部都得以执行,要么就全部都不执行,就如同一个不可分割的整体。

接下来,咱们结合日常实际开发场景来深入聊聊事务。

在我们所开发的系统里,每个业务方法往往包含了多个原子性的数据库操作。就拿下面这个 savePerson() 方法来说吧:

public void savePerson() {
    // 这里首先通过personDao将人员基本信息保存到数据库
    personDao.save(person);
    // 接着通过personDetailDao将人员详细信息也保存到数据库
    personDetailDao.save(personDetail);
}

可以看到,在这个方法中,对人员基本信息和详细信息的保存操作,它们各自都是原子性的数据库操作,而且这两个操作之间是存在依赖关系的哦。也就是说,它们必须要么同时成功完成,要么同时不执行,这样才能保证数据的完整性和一致性。

这里还得特别强调一点,那就是事务能否真正生效,数据库引擎是否支持事务可是至关重要的因素呀。就拿我们常用的 MySQL 数据库来说吧,它默认使用的是支持事务的 InnoDB 引擎。但要是把数据库引擎换成了 MyISAM,那可就麻烦啦,程序在这种情况下可就不再支持事务咯。

说到事务,最经典且常常被拿来举例的就是转账操作啦。想象一下,小明要给小红转账 1000 元呢,这看似简单的一个转账行为,实际上会涉及到两个关键的操作步骤哦:

第一步,得把小明账户的余额减少 1000 元;

第二步,要将小红账户的余额增加 1000 元。

可要是在这两个操作进行的过程当中,突然出现了一些意外情况,比如说银行系统崩溃啦,或者网络出现故障之类的,结果导致小明的余额已经减少了,但是小红的余额却没有增加,这显然是不对的呀,数据就出现不一致啦。而事务的存在呢,就是为了确保这两个关键操作要么都能够成功完成,要么一旦出现问题就都全部失败回滚,从而保证数据始终处于正确的状态。

再来看下面这个 OrdersService 类中的 accountMoney() 方法,它也是一个很好的例子呢:

public class OrdersService {
    private AccountDao accountDao;

    // 通过这个方法设置AccountDao实例,方便后续数据库操作
    public void setOrdersDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Transactional(propagation = Propagation.REQUIRED,
                    isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
    public void accountMoney() {
        // 首先,让小红的账户余额增加1000元
        accountDao.addMoney(1000, "xiaohong");

        // 这里模拟一个突然出现的异常情况,就好比银行里可能突然停电之类的情况哦
        // 如果没有配置合适的事务管理机制的话,就会出现问题啦,比如会造成小红账户多了1000元,而小明账户却没有少钱的情况
        int i = 10 / 0;

        // 最后,让小王的账户余额减少1000元
        accountDao.reduceMoney(1000, "xiaoming");
    }
}

在这个方法里,如果没有事务管理的保障,一旦中间出现异常,就很可能导致数据出现不一致的情况。但有了 @Transactional 注解来标记这个方法,就意味着这个方法内的操作会在事务的管控之下进行啦。要是在执行过程中出现异常,事务就会自动按照设定的规则进行回滚操作,从而保证数据的正确性和一致性呢。

二、事务的四大特性了解么?

事务在逻辑上是一组操作,这些操作要么全部执行,要么全部不执行。它主要是针对数据库而言的,比如MySQL。为了保证事务是正确可靠的,在数据库进行写入或者更新操作时,就必须表现出ACID的4个重要特性:

  1. 原子性Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

  2. 一致性Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;

  3. 隔离性Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

  4. 持久性Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

三、事务的隔离级别

  1. 未提交读(Read uncommitted):最低的隔离级别,允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。

  2. 提交读(Read committed):一个事务可能会遇到不可重复读(Non-Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。

  3. 可重复读(Repeatable read):一个事务可能会遇到幻读(Phantom Read)的问题。幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就出现了。

  4. 串行化(Serializable):最严格的隔离级别,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。虽然Serializable隔离级别下的事务具有最高的安全性,但是由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。    

四、Spring支持两种方式事务过滤

1、声明式事务管理​

推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。

使用 @Transactional注解进行事务管理的示例代码如下:

import org.springframework.transaction.annotation.Transactional;

@Transactional(rollbackFor = Exception.class)
public class AccountService {
    public void transferMoney(int fromAccountId, int toAccountId, double amount) {
        // 从一个账户扣款
        accountDao.withdraw(fromAccountId, amount);
        // 向另一个账户收款
        anotherAccountDao.deposit(toAccountId, amount);
    }
}

2、编程式事务管理

  • 除了声明式事务管理外,Spring还支持编程式事务管理,即通过编写代码来控制事务的开始、提交和回滚。

  • 可以使用TransactionTemplatePlatformTransactionManager来实现编程式事务管理。

  • 示例(使用TransactionTemplate):

@Service
public class TransactionalService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private UserRepository userRepository;

    public void performTransactionalOperation() {
        transactionTemplate.execute(status -> {
            try {
                userRepository.save(new User(/* 参数 */));
                // 其他数据库操作
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
            return null;
        });
    }
}

Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • PlatformTransactionManager(平台)事务管理器,Spring 事务策略的核心。

  • TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。

  • TransactionStatus:事务运行状态。

我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

五、Spring 事务管理接口介绍

1、transactionStatus接口

TransactionStatus接口表示了一个具体的事务的状态信息。在进行事务操作时,可以通过TransactionStatus获取当前事务的一些状态信息,如是否已经完成、是否已经回滚等。它主要用于监控或控制事务的执行过程。

TransactionStatus接口包含以下几个主要方法:

  1. boolean isCompleted()检查事务是否已完成(即已提交或已回滚)。

  2. boolean hasRollbackOccurred()检查事务是否已回滚。

  3. boolean isNewTransaction()检查当前事务是否是新事务。

  4. boolean isRollbackOnly()检查事务是否标记为仅回滚。

  5. void setRollbackOnly()将事务标记为仅回滚。

  6. boolean isActive()检查事务是否仍然活跃(即尚未完成)。

public interface TransactionStatus extends SavepointManager {
    // 检查事务是否已完成(即已提交或已回滚)
    boolean isCompleted();
    
    // 检查事务是否已回滚
    boolean hasRollbackOccurred();
    
    // 检查当前事务是否是新事务
    boolean isNewTransaction();
    
    // 检查事务是否标记为仅回滚
    boolean isRollbackOnly();
    
    // 将事务标记为仅回滚
    void setRollbackOnly();
    
    // 检查事务是否仍然活跃(即尚未完成)
    boolean isActive();
    
    // 返回当前事务的隔离级别
    int getIsolationLevel();
    
    // 返回当前事务是否为只读事务
    boolean isReadOnly();
    
    // 其他状态检查方法...
}

2、TransactionDefinition接口

TransactionDefinition接口用于定义事务的一些属性,如隔离级别、传播行为、超时时间等。通过配置这些属性,可以定制事务的行为以满足不同场景下的需求。

TransactionDefinition接口包含以下几个主要属性:

  1. 隔离级别(Isolation Level):定义了事务方法的操作与其他事务方法的操作之间的隔离程度。Spring提供了多种隔离级别,如DEFAULT(默认)、READ_UNCOMMITTED(读未提交)、READ_COMMITTED(读已提交)、REPEATABLE_READ(可重复读)和SERIALIZABLE(串行化)等。

  2. 传播行为(Propagation Behavior):定义了事务方法被调用时如何处理现有的事务。Spring提供了多种传播行为,如REQUIRED(必须)、REQUIRES_NEW(要求新事务)、SUPPORTS(支持)、NOT_SUPPORTED(不支持)、MANDATORY(必须存在)、NEVER(永不)和NESTED(嵌套)等。

  3. 超时时间(Timeout):定义了事务必须在多少秒内完成。如果事务执行时间超过了这个限制,则会自动回滚。

  4. 只读标志(Read-Only Flag):标记事务是否为只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段来提高性能。

public interface TransactionDefinition {
    // 返回事务的传播行为
    int getPropagationBehavior();
    
    // 返回事务的隔离级别
    int getIsolationLevel();
    
    // 返回事务必须在多少秒内完成
    int getTimeout();
    
    // 返回事务的名字
    String getName();
    
    // 返回是否优化为只读事务
    boolean isReadOnly();
    
    // 事务隔离级别的常量定义
    int ISOLATION_DEFAULT = ...; // 使用后端数据库默认的隔离级别
    int ISOLATION_READ_UNCOMMITTED = ...; // 最低的隔离级别,允许读取尚未提交的数据变更
    int ISOLATION_READ_COMMITTED = ...; // 允许读取并发事务已经提交的数据
    int ISOLATION_REPEATABLE_READ = ...; // 对同一字段的多次读取结果都是一致的
    int ISOLATION_SERIALIZABLE = ...; // 最高的隔离级别,完全服从ACID的隔离级别
    
    // 事务传播行为的常量定义
    int PROPAGATION_REQUIRED = ...; // 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
    int PROPAGATION_SUPPORTS = ...; // 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
    int PROPAGATION_MANDATORY = ...; // 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
    int PROPAGATION_REQUIRES_NEW = ...; // 创建一个新的事务,如果当前存在事务,则把当前事务挂起
    int PROPAGATION_NOT_SUPPORTED = ...; // 以非事务方式运行,如果当前存在事务,则把当前事务挂起
    // 其他传播行为常量...
}

3、PlatformTransactionManager接口

PlatformTransactionManager是Spring事务管理的核心接口,它定义了事务管理的一些基本操作,如开启事务、提交事务、回滚事务等。这个接口是实际事务管理器(如JDBC事务管理器、JTA事务管理器等)的统一入口,并为上层的业务逻辑屏蔽了不同事务管理器的实现细节。

具体来说,PlatformTransactionManager接口包含以下几个主要方法:

  1. TransactionStatus getTransaction(TransactionDefinition definition)根据给定的事务定义(TransactionDefinition)来获取一个事务状态(TransactionStatus)对象。这个对象用于表示当前事务的状态,并允许在事务执行过程中进行状态检查和操作。

  2. void commit(TransactionStatus status)提交事务。如果事务成功执行,则调用此方法将更改持久化到数据库。

  3. void rollback(TransactionStatus status)回滚事务。如果事务执行过程中出现异常或错误,则调用此方法将更改撤销,恢复到事务开始之前的状态。

public interface PlatformTransactionManager {
    // 根据指定的传播行为和定义获取一个事务状态对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    
    // 提交事务
    void commit(TransactionStatus status) throws TransactionException;
    
    // 回滚事务
    void rollback(TransactionStatus status) throws TransactionException;
}

六、@Transactional 注解使用详解

1、@Transactional注解的基本使用

@Transactional注解可以应用于接口、接口方法、类以及类方法上。但需要注意以下几点:

  1. 接口和接口方法:虽然@Transactional注解可以应用于接口和接口方法上,但通常不建议这样做。这是因为只有当使用基于接口的动态代理时,这些注解才会生效。而在实际开发中,基于类的代理(如CGLIB)更为常见。因此,将@Transactional注解应用于具体的实现类上更为稳妥。

  2. 类级别:当@Transactional注解应用于类上时,该类的所有public方法都将按照相同的事务属性进行处理。这意味着,如果你希望在类的不同方法中使用不同的事务属性,那么你需要将这些方法拆分为不同的类,并为每个类指定不同的事务属性。

  3. 方法级别:当@Transactional注解应用于方法上时,仅对该方法内的数据库操作生效。这是最常见和推荐的使用方式,因为它允许开发者为不同的业务操作指定不同的事务属性。

2、@Transactional注解的作用范围

@Transactional注解的作用范围主要取决于其应用的位置(类级别或方法级别)以及Spring的AOP(面向切面编程)机制。以下是一些关键点:

  1. AOP代理:Spring通过AOP代理来处理带有@Transactional注解的方法。当这些方法被调用时,Spring会自动开启一个事务,并在方法执行完成后提交或回滚事务。需要注意的是,这个代理过程仅对来自外部的方法调用有效。如果类内部的方法调用本类内部的其他带有@Transactional注解的方法,那么这个调用将不会触发事务管理(因为此时调用的是原始对象的方法,而不是代理对象的方法)。

  2. public方法:@Transactional注解只能应用于public方法上。如果将其应用于protected、private或默认修饰符的方法上,虽然不会报错,但注解将不会生效。这是因为Spring的AOP代理只能代理public方法。

  3. 异常处理:默认情况下,@Transactional注解只能回滚运行时异常(RuntimeException及其子类)和错误(Error子类)。如果希望回滚其他类型的异常(如受检异常Checked Exception),则需要通过rollbackFor属性来指定。同时,需要注意的是,如果异常被try-catch块捕获并处理,那么事务将不会回滚(除非在catch块中显式地触发回滚)。

3、@Transactional 的常用配置参数

@Transactional注解源码如下,里面包含了基本事务属性的配置:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

  @AliasFor("transactionManager")
  String value() default "";

  @AliasFor("value")
  String transactionManager() default "";

  Propagation propagation() default Propagation.REQUIRED;

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

}

@Transactional 的常用配置参数总结(只列出了 5 个我平时比较常用的):

属性名说明
propagation事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过
isolation事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过
timeout事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly指定事务是否为只读事务,默认值为 false。
rollbackFor用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。

@Transactional 事务注解原理

@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。

createAopProxy() 方法 决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

  @Override
  public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
        throw new AopConfigException("TargetSource cannot determine target class: " +
            "Either an interface or a target is required for proxy creation.");
      }
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
        return new JdkDynamicAopProxy(config);
      }
      return new ObjenesisCglibAopProxy(config);
    }
    else {
      return new JdkDynamicAopProxy(config);
    }
  }
}

如果一个类或者一个类中的 public 方法上被标注@Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

七、案例

1、引入依赖

首先,确保你的项目中引入了Spring事务管理相关的依赖。如果你使用的是Maven,可以在pom.xml中添加以下依赖:

<dependencies>

    <!-- Spring Context, Core, and other necessary dependencies -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.10</version>
    </dependency>
    <!-- JDBC Driver and DataSource, e.g., H2 Database -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.200</version>
    </dependency>

</dependencies>

2、配置数据源和事务管理器

在Spring配置文件中(可以是XML文件或Java配置类),配置数据源(DataSource)和事务管理器(PlatformTransactionManager)。

XML配置示例

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/tx 
           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- DataSource configuration -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:mem:testdb"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <!-- PlatformTransactionManager configuration -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Enable annotation-driven transaction management -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

Java配置示例

@Configuration
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:testdb");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

3、使用@Transactional注解

在需要事务支持的方法上使用@Transactional注解。这个注解可以放在类级别(表示类中的所有public方法都受事务管理)或方法级别(仅该方法受事务管理)。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // 这里可以添加其他数据库操作,它们都会在同一个事务中执行
    }

    // 其他业务方法...
}

在上面的示例中,createUser方法被@Transactional注解标记,这意味着Spring将在这个方法执行前后开启和提交事务。如果方法执行过程中抛出了未捕获的异常,事务将被回滚。

4、事务属性

@Transactional注解还支持多种属性来定制事务行为,如隔离级别、传播行为、超时时间等。

@Transactional(isolation = Isolation.READ_COMMITTED,
               propagation = Propagation.REQUIRED,
               timeout = 30) // 超时时间为30秒
public void someTransactionalMethod() {
    // 方法实现
}

5、异常处理与回滚

默认情况下,Spring会在运行时异常(RuntimeException及其子类)和错误(Error及其子类)发生时回滚事务。对于受检异常(checked exceptions),你需要明确指定哪些异常应该导致事务回滚。

@Transactional(rollbackFor = {CustomException.class, AnotherException.class})
public void someMethodThatMayThrowCheckedExceptions() {
    // 方法实现,可能会抛出CustomException或AnotherException
}

6、测试事务

最后,确保你的事务配置正确无误,并编写单元测试来验证事务的行为。你可以使用Spring的测试框架(如@RunWith(SpringRunner.class)@SpringBootTest)来运行测试,并使用@Transactional注解来模拟事务环境

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yaml墨韵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值