侧边栏壁纸
博主头像
快乐江湖的博客博主等级

更多内容请点击CSDN关注“快乐江湖”

  • 累计撰写 127 篇文章
  • 累计创建 33 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

第十二章第一节:Spring事务和@Transactioal

快乐江湖
2023-12-10 / 0 评论 / 0 点赞 / 13 阅读 / 27900 字

一:回顾事务的基本概念

(1)事务

A:定义

事务:是用户定义的一个数据库操作序列。这些操作要么不做,要么全做,是一个不可分割的工作单位。例如在RDBMS中一个事务可以是一条SQL语句或整个程序。事务是数据库恢复和并发控制的基本单位

  • 事务和程序的区别:一般来说,一个程序中包含多个事务

B:事务的定义

事务的定义:事务的开始与结束由用户显式控制。如果用户没有显式地定义事务,则由DBMS按默认规定自动划分事务。在SQL中,定义事务语句有以下三条

  • BEGIN TRANSACTION:表示事务的开始
  • COMMIT:表示事务的正常结束并提交事务的所有操作
  • ROLLBACK:表示事务的结束,但没有正常结束,需要进行回滚(撤销已完成操作,使系统恢复至回滚前状态)

注意不同数据库系统定义语句有所区别

SQL Server

BEGIN TRANSACTION
COMMIT | ROLLBACK

MySQL

START TRANSACTION
COMMIT | ROLLBACK

Oracle

START TRANSACTION NAME
COMMIT | ROLLBACK

(2)事务的四个特性——ACID

A:数据库的ACID

①:原子性(Atomicity)

原子性:事务是数据库的逻辑工作单位,事务中包含的诸多操作要么全做、要么不做。因故障未能做完的,需要有一套机制用于“撤销”那一部分已经做了的

②:一致性(Consistency)

一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态

  • 一致性状态:数据库中只包含成功事务提交的结果
  • 不一致状态:数据库中包含事务未完成时的状态

例如银行转账业务,账户A转1万元到账户B,该事务包含两个操作:首先是A减少一万元,其次是B增加一万元,这两个操作要么全部做要么全不做,如果只做其中一个就会发生逻辑错误,数据库就处于不一致状态了

③:隔离性(Isolation)

隔离性:一个事务不能被其他事务干扰。也即一个事务的内部操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰

比如,下列两个并发执行的事务T1和T2,如按表中所示顺序执行,则事务T1的修改被T2覆盖了,即T2干挠了T1。违背了事务的隔离性,是错误的调度

④:持续性(Durability)

持续性:一个事务一旦提交,它对数据库中数据的改变就是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响

B:破坏ACID的因素

主要有两类

  • 故障:没有执行完;虽然没有完,但是存储介质故障。破坏了ACID中的ACD
  • 并发干扰:多个事务并行运行时,不同事务的操作交叉执行,互相干扰。破坏了ACID中的I

因此这就是DBMS的恢复机制并发控制机制需要解决的问题

二:Spring中事务的实现

Spring事务:在Spring框架中,事务管理是一个核心功能,它提供了一种一致的编程模型,支持不同的事务管理策略。Spring的事务管理旨在提供一种对于不同持久化技术(如JDBC, Hibernate, JPA等)的统一事务处理方式。分为两类

  • 编程式事务:手动写代码操作事务
  • 声明式事务:利用注解自动开启和提交事务

(1)Spring编程式事务

Spring编程式事务:编程式事务管理是一种将事务管理代码直接嵌入到业务代码中的方法。与声明式事务管理相比,编程式事务管理给予开发者更多的控制权,但同时也增加了代码的复杂性:

  • DataSourceTransactionManager(JDBC):这是Spring提供的事务管理器接口,用于具体的事务管理操作,如开始、提交和回滚事务
    • 提交事务:dataSourceTransactionManager.commit(transactionStatus)
    • 回滚事务:dataSourceTransactionManager.rollback(transactionStatus)
  • TransactionDefinition:定义了事务的各种属性,如隔离级别、传播行为、超时设置等
  • TransactionStatus:用于跟踪当前事务的状态,包括是否有新事务的创建、是否已完成等信息。也可以用来编程式地触发回滚。
    • TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition)

例子:仍然使用我们在学习MyBatis时的案例,以插入User为例,userinfo表初始状态如下

UserController.java

package com.example.demo.controller;  
  
  
import com.example.demo.entity.UserInfo;  
import com.example.demo.service.UserService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.jdbc.datasource.DataSourceTransactionManager;  
import org.springframework.transaction.TransactionDefinition;  
import org.springframework.transaction.TransactionStatus;  
import org.springframework.util.StringUtils;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
import java.time.LocalDateTime;  
  
@RestController  
@RequestMapping("/user")  
public class UserController {  
    @Autowired  
    private UserService userService;  
    @Autowired  
    private DataSourceTransactionManager dataSourceTransactionManager; // JDBC事务管理器  
    @Autowired  
    private TransactionDefinition transactionDefinition; // 用于定义事务属性  
  
    @RequestMapping("/add")  
    public int add (UserInfo userInfo) {  
        // 非空校验  
        if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) {  
            return 0;  
        }  
  
        // 开始事务  
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);  
  
        // 业务代码  
        userInfo.setCreatetime(LocalDateTime.now().toString());  
        userInfo.setUpdatetime(LocalDateTime.now().toString());  
        int result = userService.add(userInfo);  
  
        // ①:提交事务  
        // dataSourceTransactionManager.commit(transactionStatus);  
  
        // ②:回滚事务  
        // dataSourceTransactionManager.rollback(transactionStatus);  
        return result;  
    }  
}

①:插入一条用户数据,并选择提交事务

②:插入一条用户数据,并选择回滚事务

(2)Spring声明式事务

A:概述

Spring声明式事务:借助注解@Transactional完成,可以加在类上或方法。具体来说,当加上这个注解后,方法执行前会自动开启事务,在方法执行完之后(无异常)自动提交事务,如果中途发生了没有处理的异常会自动回滚事务

  • 修饰方法:只能应用在public方法上,否则不生效
  • 修饰类:对类中所有public方法都生效

例子:如下,故意设置一个异常,然后在有无@Transactional下进行对比

UserController.java

package com.example.demo.controller;  
import com.example.demo.entity.UserInfo;  
import com.example.demo.service.UserService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.transaction.annotation.Transactional;  
import org.springframework.util.StringUtils;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
import java.time.LocalDateTime;  
  
@RestController  
@Transactional // 声明式事务  
@RequestMapping("/user")  
public class UserController {  
    @Autowired  
    private UserService userService;  
  
  
    @RequestMapping("/add")  
    public int add (UserInfo userInfo) {  
        // 非空校验  
        if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) {  
            return 0;  
        }  
  
  
        // 业务代码  
        userInfo.setCreatetime(LocalDateTime.now().toString());  
        userInfo.setUpdatetime(LocalDateTime.now().toString());  
        int result = userService.add(userInfo);  
  
        // 故意设置异常  
        int num = 10 / 0;  
        return result;  
    }  
}

没有@Transactional


加上@Transactional

B:@Transactional参数说明

@Transactional有很多参数可以使用,应用于不同的业务场景

  • value:当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器
  • transactionManager:当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器
  • propagation:事务的传播行为,默认值为Propagation.REQUIRED
  • isolation:事务的隔离级别,默认值为Isolation.DEFAULT
  • timeout:事务的超时时间,默认值为-1,表示不设置。当超过设定时间后,如果事务还是没有完成,则自动回滚
  • readOnly:指定事务是否为只读事务,默认为false
  • rollbackFor:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型
  • rollbackForClassName:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型
  • noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型
  • noRollbackForClassName:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

C:关于事务不会自动回滚的解决方案

@Transactional 在默认情况下只会在未捕获的异常上自动触发事务回滚。如果异常被捕获并且没有重新抛出,事务就不会回滚

例如下面的例子中,事务没有回滚

package com.example.demo.controller;  
import com.example.demo.entity.UserInfo;  
import com.example.demo.service.UserService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.transaction.annotation.Transactional;  
import org.springframework.util.StringUtils;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
import java.time.LocalDateTime;  
  
@RestController 
@Transactional
@RequestMapping("/user")  
public class UserController {  
    @Autowired  
    private UserService userService;  
  
  
    @RequestMapping("/add")  
    public int add (UserInfo userInfo) {  
        // 非空校验  
        if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) {  
            return 0;  
        }  
  
  
        // 业务代码  
        userInfo.setCreatetime(LocalDateTime.now().toString());  
        userInfo.setUpdatetime(LocalDateTime.now().toString());  
        int result = userService.add(userInfo);  
  
        // 故意设置异常并捕获异常  
        try {  
            int num = 10 / 0;  
        } catch (Exception e) {  
            System.out.println(e.getMessage());  
        }  
        return result;  
    }  
}

解决方法有两种

  • 显式抛出异常:在捕获到异常后,手动抛出一个新的未捕获异常,以触发事务回滚
  • 手动回滚异常:使用TransactionAspectSupport.currentTransactionStatus()得到当前事务,然后调用setRollbackOnly()进行回滚
// 重新抛出异常
try {  
    int num = 10 / 0;  
} catch (Exception e) {  
    System.out.println(e.getMessage());  
    throw e;  
}

// 手动回滚异常
try {  
    int num = 10 / 0;  
} catch (Exception e) {  
    System.out.println(e.getMessage());  
    // 得到当前事务然后回滚  
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  
}
0

评论区