Spring 6(三)

Spring 事务

数据库事务

数据库事务(transaction)是一组数据库的操作,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。事务具有以下特性:

  • 原子性(Atomicity):事务被视为不可分割的最小工作单位,事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的部分操作,这就是事务的原子性。例如,在银行账户转账中,从A账户扣款和向B账户存款这两个操作要么都成功,要么都失败,不会只执行其中一个操作。
  • 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态。一致性与原子性是密切相关的。事务在执行前后,数据库都必须处于一致性状态。这意味着事务执行的结果必须是合法的,即满足所有的完整性约束。以转账为例,转账前后,两个账户的总金额应该是一致的,不会凭空多或少了一部分。
  • 隔离性(Isolation):数据库系统提供一定级别的隔离,使得多个事务并发执行时,一个事务的执行不会被其他事务的干扰。隔离性通过锁机制和并发控制来实现。不同的隔离级别对应不同的事务干扰程度,常见的隔离级别有:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
  • 持久性(Durability):指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

前置准备

Spring 中支持声明式事务和编程式事务,要使用 Spring 事务,需要引入以下依赖:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
</dependency>

基本概念

TransactionManager

org.springframework.transaction.TransactionManager

TransactionManager 是一个标记接口,该接口没有任何方法,用于标记一个类是一个事务管理器。

Spring 通过 TransactionManager 的子接口 PlatformTransactionManager 接口来实际管理事务。

PlatformTransactionManager

org.springframework.transaction.PlatformTransactionManager

PlatformTransactionManager 定义了与事务相关的基本操作方法,包括开始、提交和回滚事务。

PlatformTransactionManager 接口定义了以下三个核心方法:

  • TransactionStatus getTransaction (TransactionDefinition definition)
    • 描述:根据传入的 TransactionDefinition,获取当前事务的状态。如果存在现有事务且传播行为允许,则返回当前事务;否则,开启一个新事务。
    • 参数:TransactionDefinition definition,事务的定义,包括传播行为、隔离级别、超时时间等。
    • 返回值:TransactionStatus 对象,表示当前事务的状态。
  • void commit (TransactionStatus status)
    • 描述:提交事务。如果事务中有任何更改,它们将被持久化到数据库。
    • 参数:TransactionStatus status,表示当前事务的状态。
  • void rollback (TransactionStatus status)
    • 描述:回滚事务,撤销事务中的所有更改。
    • 参数:TransactionStatus status,表示当前事务的状态。

基于注解实现声明式事务

声明式事务是一种通过配置来管理事务的方式,而不是通过编程方式。它允许开发者将事务管理代码从业务逻辑代码中分离出来,从而提高了代码的可读性和可维护性。Spring 通过 AOP(面向切面编程)来实现声明式事务管理,使得事务管理变得透明和灵活。

事务管理流程

  1. 使用 @EnableTransactionManagement 开启事务功能
  2. 在 Spring 容器启动时,会创建一个 TransactionInterceptor 类型的 bean
  3. 接下来,Spring 容器在扫描所有 @Component 的组件(包括派生的,例如 @Service)时,会顺带扫描并解析 @Transactional。如果发现 @Transactional 标注的类,则会创建代理对象,将 TransactionInterceptor 的功能加强到被代理的对象,扫描 TransactionInterceptor 这一步在 SpringTransactionAnnotationParser 这个类完成。
  4. Spring 使用 AOP 机制创建代理对象,通过代理模式将 TransactionInterceptor 植入被代理的对象。
  5. 当调用事务方法,Spring 的事务拦截器(TransactionInterceptor)会被触发,事务拦截器会根据当前事务上下文和传播行为(Propagation),决定是创建一个新的事务,还是加入现有的事务。假设当前没有事务上下文,并且方法使用默认的 REQUIRED 传播行为,那么拦截器会创建一个新的事务。
  6. 方法执行完毕后,控制权返回到代理对象的事务拦截器。拦截器会根据方法的执行情况(是否抛出了异常)决定提交或回滚事务。3

使用步骤

  1. 添加事务依赖
  2. 设法(配置类或 XML)配置一个 TransactionManager,下面是一个 XML 的例子:
    <?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: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">
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="druidDataSource"/>
        </bean>
        <!--
            开启事务的注解驱动
            通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
        -->
        <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    </beans>
    
  3. 添加事务注解 @Transactional 在需要事务的方法上,@Transactional 标识在方法上,则只会影响该方法,@Transactional 标识的类上,则会影响类中所有的方法。

@Transactional

只读

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化:

@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
}

超时

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是 Java 程序或 MySQL 数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

概括来说就是一句话:超时回滚,释放资源。

//超时时间单位秒
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
}

回滚策略

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

可以通过@Transactional中相关属性设置回滚策略

  • rollbackFor 属性:需要设置一个Class类型的对象

  • rollbackForClassName 属性:需要设置一个字符串类型的全类名

  • noRollbackFor 属性:需要设置一个Class类型的对象

  • rollbackFor 属性:需要设置一个字符串类型的全类名

隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:

  • 读未提交(READ UNCOMMITTED):允许一个事务读取到另一个事务未提交的修改。

  • 读已提交(READ COMMITTED): 要求一个事务只能读取到另一个事务已提交的修改。

  • 可重复读(REPEATABLE READ):确保一个事务可以多次从一个字段中读取到相同的值,即该事务执行期间禁止其它事务对这个字段进行更新。

  • 串行化(SERIALIZABLE):确保一个事务可以多次从一个表中读取到相同的行,在该事务执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产品对事务隔离级别的支持程度:

隔离级别OracleMySQL
READ UNCOMMITTED×
READ COMMITTED√ (默认)
REPEATABLE READ×√ (默认)
SERIALIZABLE

使用方式:

@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

传播行为

在 service 类中有 a() 方法和 b() 方法,a() 方法上有事务,b() 方法上也有事务,当 a() 方法执行过程中调用了 b() 方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

一共有七种传播行为:

  • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行 【有就加入,没有就不管了】
  • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常 【有就加入,没有就抛异常】
  • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起 【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
  • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务 【不支持事务,存在就挂起】
  • NEVER:以非事务方式运行,如果有事务存在,抛出异常 【不支持事务,存在就抛异常】
  • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和 REQUIRED 一样。】

可以通过 @Transactional 中的 propagation 属性设置事务传播行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值