简单了解Spring事务

🌴简单了解Spring事务

🍉一、什么是事务,有什么作用?

事务(Transaction)是指作为单个逻辑工作单元执行的一系列操作。在数据库管理系统中,事务是对数据库执行的一组操作的逻辑单元,它被视为一个独立的工作单元,要么完全执行,要么完全不执行。事务是确保数据库数据的一致性、完整性和持久性的重要机制之一。

事务具有以下四个关键属性,通常被称为ACID属性:

  1. 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败。如果在事务执行期间发生了错误,所有的更改将被回滚,数据库状态将被还原到事务开始之前的状态。

  2. 一致性(Consistency):事务执行后,数据库的状态必须满足所有的约束和规则,保证数据的一致性。这意味着事务执行的结果必须是有效的,不能违反数据库的完整性约束。

  3. 隔离性(Isolation):并发执行的多个事务之间应该相互隔离,使得每个事务感觉到它是在系统中独立执行的,即使在实际执行中,多个事务同时运行。

  4. 持久性(Durability):一旦事务成功完成并提交,其结果应该是永久性的,即使系统发生故障,数据库也应该能够恢复到事务成功完成后的状态。

事务的使用可以确保数据库操作的一致性和可靠性。例如,在进行银行转账时,必须保证从一个账户扣款和向另一个账户存款是原子操作,即使在系统发生故障时也不会出现资金损失或数据不一致的情况。因此,事务是数据库管理系统中的重要概念,用于确保数据的完整性和可靠性。

本文讲的是Spring事务,Spring 只提供统一事务管理接口,实现都是数据库自己去实现提交、回滚。

🥝二、事务的种类

现在都用声明式事务,编程式事务停留在大学教学使用仅做了解吧

在这里插入图片描述

编程式事务

编程式事务管理是指通过编写代码显式地管理事务的开启、提交、回滚和关闭等操作。下面是 Spring 中编程式事务管理的基本步骤:

  1. 获取事务管理器(PlatformTransactionManager):首先,需要获取一个适当的事务管理器实例,该实例负责管理事务的生命周期。Spring 提供了各种类型的事务管理器,如 DataSourceTransactionManager、JpaTransactionManager 等,具体选择取决于你使用的持久化技术。
  2. 开启事务:在执行需要进行事务管理的代码之前,通过调用事务管理器的方法来开启一个事务。
  3. 执行事务操作:在事务开启之后,执行需要在同一个事务中执行的数据库操作。这些操作可以是针对数据库的增删改查等操作。
  4. 提交或回滚事务:根据业务逻辑的执行结果,决定是提交事务还是回滚事务。如果操作成功,调用事务管理器的提交方法提交事务;如果发生异常或操作失败,调用事务管理器的回滚方法回滚事务。
  5. 关闭事务:当事务执行完成后,需要关闭事务。通常,Spring 会在执行完提交或回滚操作后自动关闭事务,但也可以显式地调用事务管理器的关闭方法来手动关闭事务。

下面是一个简单的示例,演示了如何在 Spring 中使用编程式事务管理:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Service
public class UserService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private UserRepository userRepository;

    public void transferMoney(int fromUserId, int toUserId, double amount) {
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);
        
        try {
            User fromUser = userRepository.findById(fromUserId);
            User toUser = userRepository.findById(toUserId);
            
            if (fromUser.getBalance() >= amount) {
                fromUser.setBalance(fromUser.getBalance() - amount);
                toUser.setBalance(toUser.getBalance() + amount);
                
                userRepository.save(fromUser);
                userRepository.save(toUser);
                
                transactionManager.commit(status);
            } else {
                throw new InsufficientBalanceException("Insufficient balance");
            }
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw new TransactionFailedException("Transaction failed", e);
        }
    }
}

声明式事务

通过@Transactional注解即可,spring帮你管理

@Service
public class UserService {

    @Transactional
    public void transferMoney(int fromUserId, int toUserId, double amount) {
        ...
    }
}

后文的隔离级别、传播机制都可以通过这个注解指定

@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)

🍅三、事务的隔离级别

事务的隔离级别是指多个事务并发执行时,各个事务之间的相互影响程度的规定。数据库管理系统需要考虑并发执行时可能产生的数据一致性问题,而隔离级别就是为了解决这些问题而定义的。

常见的数据库隔离级别有四种:

  1. 读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务尚未提交的数据。这种隔离级别可能导致脏读(Dirty Read),即一个事务读取到了另一个事务未提交的数据。

  2. 读已提交(Read Committed):确保一个事务只能读取到已提交的数据。这种隔离级别可以避免脏读,但仍然可能出现不可重复读(Non-Repeatable Read)问题,即一个事务在多次读取同一数据时,由于其他事务的更新操作,可能读取到了不同的值。

  3. 可重复读(Repeatable Read):确保一个事务在多次读取同一数据时,所读取到的值是一致的。在该隔离级别下,事务在读取数据时会对所涉及的数据加锁,防止其他事务修改数据,因此可以避免不可重复读问题。但仍然可能出现幻读(Phantom Read)问题,即一个事务在读取数据时,由于其他事务的插入或删除操作,可能读取到了新增或删除的数据。

  4. 串行化(Serializable):最高的隔离级别,确保每个事务串行执行,相当于对所有的数据操作加上了锁。这种隔离级别可以避免所有的并发问题,包括脏读、不可重复读和幻读,但性能通常会受到影响。

不同的隔离级别在提供数据一致性和并发性方面有不同的权衡。通常来说,隔离级别越高,数据的一致性就越好,但并发性就越差,因为需要更多的加锁操作来保证数据的一致性。在选择隔离级别时,需要根据具体的业务需求和系统性能进行权衡取舍。在大多数情况下,使用默认的隔离级别(通常是读已提交)即可满足需求。

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
}

spring的隔离级别枚举类里面多了一个DEFAULT,通常用默认的就行了。

🍊四、事务的传播机制

事务的传播机制是指在多个事务方法相互调用的情况下,各个方法之间事务如何传播和交互的规则。Spring 提供了丰富的事务传播行为,以满足不同业务场景下的需求。

以下是 Spring 提供的事务传播行为:

  1. PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的传播行为,适用于大多数业务场景。

  2. PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,则以非事务方式执行。

  3. PROPAGATION_MANDATORY:强制要求当前存在事务,如果当前没有事务,则抛出异常。

  4. PROPAGATION_REQUIRES_NEW:创建一个新的事务,并且暂停当前事务(如果存在)。如果当前存在事务,则新事务将在它结束后恢复。

  5. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则暂停事务。

  6. PROPAGATION_NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。

  7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则执行与 REQUIRED 类似的操作。但是,如果嵌套事务内部发生异常,只会回滚嵌套事务,而不会回滚外部事务。

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
}

🥑五、事务不生效的清况

在这里插入图片描述

  1. spring事务拦截器,aop只会获取public修饰的方法上的@transactional注解信息
  2. 调用其他类方法才会走代理类
  3. 不合适的传播机制
  4. 抛出的不是rollbackFor设置的指定异常

针对第三种情况(传播机制设置错误),补充说明,Spring事务如果要问,绕不开这个:

[ZxFkService]
public void noTransactional() {
        ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
        insert(build);
        zxSxService.MethodOfOtherClass();
    }
    
[ZxSxService]
@Transactional
    public void MethodOfOtherClass() {
        ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
        zxsxMapper.insert(build);
        if (build.getBh().equals("001")) {
            throw new RuntimeException("error");
        }
    }

正确答案:fk记录不回滚,sx回滚

[ZxFkService]
@Transactional
    public void noTransactional() {
        ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
        insert(build);
        zxSxService.MethodOfOtherClass();
    }
[ZxSxService]
public void MethodOfOtherClass() {
        ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
        zxsxMapper.insert(build);
        if (build.getBh().equals("001")) {
            throw new RuntimeException("error");
        }
    }

正确答案:都回滚

[ZxFkService]
@Transactional
    public void noTransactional() {
        ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
        insert(build);
        zxSxService.MethodOfOtherClass();
    }
[ZxSxService]
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodOfOtherClass() {
        ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
        zxsxMapper.insert(build);
        if (build.getBh().equals("001")) {
            throw new RuntimeException("error");
        }
    }

正确答案:都回滚,看似是两个事务,一个主事务,一个子事务,但是没有捕获异常,就会发生回滚.

[ZxFkService]
@Transactional
public void noTransactional() {
        ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
        insert(build);
        try {
            zxSxService.MethodOfOtherClass();
        } catch (Exception e) {
            // 防止抛出异常主事务回滚
        }
    }
[ZxSxService]
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodOfOtherClass() {
        ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
        zxsxMapper.insert(build);
        if (build.getBh().equals("001")) {
            throw new RuntimeException("error");
        }
    }

正确答案:主事务不会回滚,子事务回滚

🌵六、事务的实现原理

Spring 的声明式事务是通过 AOP和代理机制实现

第一步,在 Bean 初始化阶段创建代理对象

Spring 容器在初始化单例 Bean 的时候,会遍历所有的 BeanPostProcessor 实现类,执行postProcessAfterInitialization 方法。

在执行 postProcessAfterInitialization 方法时会遍历容器中所有的切面,查找与当前 Bean 匹配的切面,这里会获取事务的属性切面,也就是 @Transactional 注解及其属性值。

然后根据得到的切面创建一个代理对象,默认使用 JDK 动态代理创建代理,如果目标类是接口,则使用 JDK 动态代理,否则使用 Cglib。

第二步,在执行目标方法时进行事务增强操作

当通过代理对象调用 Bean 方法的时候,会触发对应的 AOP 增强拦截器,声明式事务是一种环绕增强,对应接口为MethodInterceptor,事务增强对该接口的实现为TransactionInterceptor

事务拦截器TransactionInterceptorinvoke方法中,通过调用父类TransactionAspectSupportinvokeWithinTransaction方法进行事务处理,包括开启事务、事务提交、异常回滚等。

  • 30
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值