首页 > 其他分享 >Spring的事务使用教程

Spring的事务使用教程

时间:2024-01-25 16:58:31浏览次数:32  
标签:教程 读取 Spring void 事务 回滚 dataSource public

什么是事务?

事务(Transaction)是数据库操作最基本单元,逻辑上一组操作,要么都成功,要么都失败,如果操作之间有一个失败所有操作都失败 。

事务四个特性(ACID)

  • 原子性
    一组操作要么都成功,要么都失败。
  • 一致性
    一组数据从事务1合法状态转为事务2的另一种合法状态,就是一致。
  • 隔离性
    事务1操作数据,不影响事务2的操作,每一个事务之间都是隔离状态。
  • 持久性
    数据从内存加载到磁盘文件系统中,就是持久化。

事务的传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

传播行为 描述
REQUIRED 如果有事务在运行,当前的方法就在这个事务中运行,否则,启动新事务并在其中运行
REQUIRED_NEW 当前的方法必须启动新事务,并在它自己的事务内运行,如果有其它事务正在运行,则将它挂起
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有事务在运行,将它挂起
MANDATORY 当前方法必须运行在事务内,如果没有正在运行的事务,就抛出异常
NEVER 当前的方法不应该运行在事务内,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动新事务,在其内部运行

事务的隔离级别

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

  • 读未提交:READ UNCOMMITTED
    允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED
    要求Transaction01只能读取Transaction02已提交的修改。

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

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

事务的特性称为隔离性,多事务操作之间不会产生影响。不考虑隔离性会产生很多问题。
主要有三个读问题:脏读、不可重复读、虚(幻)读:

  • 脏读:一个未提交事务读取到另一个未提交事务的数据
    脏读是指一个事务在处理数据的过程中,读取到另一个未提交事务的数据。这可能导致读取到的是一个未提交的数据,而这些数据可能在后续的操作中被回滚,从而造成数据的不一致或丢失。脏读又称为无效数据读出,因为它可能读到的是一个最终不会被保存到数据库中的数据。
  • 不可重复读:一个未提交事务读取到另一提交事务修改数据
    不可重复读(Non-Repeatable Read, NRR)是指在某个事务内的多次读取操作中,由于其他事务的并发修改导致该事务读取到的数据状态发生变化,从而不能保持数据的可见性。具体来说,当一个事务多次读取相同的数据,但在每次读取之间,其他事务对其进行了修改,使得该事务在两次读取之间的数据状态不一致,这就是不可重复读的情况。
  • 幻读:一个未提交事务读取到另一提交事务添加数据
    幻读(Phantom Read, PHR)是指在某个事务的执行过程中,它依据某些查询条件读取了一些记录,随后另一个事务在该查询条件下的插入操作导致原本不会出现在结果集中的记录也被读取出来了。这种情况通常发生在事务A首先读取了一批符合特定查询条件的记录,然后在事务B插入新的记录之前,事务A再次读取这些记录,这次读取的结果包含了新插入的记录,这显然超出了事务A原来的预期,这就是所谓的幻读。
各个隔离级别解决并发问题的能力见下表:

image

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

image

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

timeout:超时时间

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。

此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常 程序可以执行。

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

默认值是 -1 ,设置时间以秒单位进行计算。
举例:

@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);
    //System.out.println(1/0);
}

如果超时,则抛出异常:

org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022

readOnly:是否只读

对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
readOnly 默认值 false,表示可以查询,可以添加修改删除操作。

设置 readOnly 值是 true,设置成 true 之后,只能查询。
举例:

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

对增删改操作设置只读会抛出下面异常:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

rollback:回滚策略

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional中相关属性设置回滚策略:

  • rollbackFor属性:设置出现哪些异常进行事务回滚
  • noRollbackFor属性:设置出现哪些异常不进行事务回滚
    举例:
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    System.out.println(1/0);
}

虽然购买图书功能中出现了数学运算异常ArithmeticException,但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行。

Spring事务管理操作

  1. 事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
  2. 在 Spring 进行事务管理操作
    1. 有两种方式:编程式事务管理和声明式事务管理
  3. 声明式事务管理
    1. 基于注解方式
    2. 基于 xml 配置文件方式
  4. 在 Spring 进行声明式事务管理,底层使用 AOP 原理
  5. Spring 事务管理 API
    提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
    image

声明式事务:基于注解方式

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出 来,进行相关的封装。

封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性 能等各个方面的优化
    所以,我们可以总结下面两个概念:
  • 编程式:自己写代码实现功能
  • 声明式:通过配置让框架实现功能
准备工作

创建数据表

CREATE TABLE `t_account` (
  `id` int NOT NULL,
  `username` varchar(15) NOT NULL,
  `money` decimal(10,0) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

创建jdbc.properties文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/dbtest1?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

配置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:context="http://www.springframework.org/schema/context" 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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--开启组件扫码-->
    <context:component-scan base-package="com.evan.spring5"/>

    <!-- 引入外部配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 配置数据源 -->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 配置jdbc模板 -->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

创建实体类、service类和dao类

//实体类
public class Account {

    private int id;
    private String username;
    private BigDecimal money;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public BigDecimal getMoney() {
        return money;
    }

    public void setMoney(BigDecimal money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", money=" + money +
                '}';
    }
}
//dao接口
public interface AccountDao {

    void reduceMoney();

    void addMoney();
}
//dao接口实现类
@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public void reduceMoney() {
        String sql = "update t_account set money = money - ? where username = ?";
        jdbcTemplate.update(sql,100,"luck");
    }

    @Override
    public void addMoney() {
        String sql = "update t_account set money = money + ? where username = ?";
        jdbcTemplate.update(sql,100,"mary");
    }
}
//service类
@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;


    public void accountMoney() {
        accountDao.reduceMoney();

        int number = 10 / 0;

        accountDao.addMoney();
    }
}
测试

此时在没有开启事务的情况下进行转账测试,可以看出在转账过程中出现异常,转账金额不一致,此时需要事务来解决,当出现转账中发送异常就会立即立即回滚数据,保证数据的一致性。

开启事务

  1. 在 spring 配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" 		
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <!--注入数据源-->
 <property name="dataSource" ref="dataSource"></property>
</bean>
  1. 在 spring 配置文件,开启事务注解
  • 引入命名空间tx
<beans xmlns="http://www.springframework.org/schema/beans" 
 	   xmlns:tx="http://www.springframework.org/schema/tx" 
       xsi:schemaLocation="http://www.springframework.org/schema/tx 
                           http://www.springframework.org/schema/tx/spring-tx.xsd"
  • 开启事务注解
<!--
开启事务的注解驱动
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />
  1. 添加事务注解
    因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理。

在 service 类上面(或者 service 类里面方法上面)添加事务注解:

(1) @Transactional这个注解添加到类上面,也可以添加方法上面

(2) 如果把这个注解添加类上面,这个类里面所有的方法都会受到影响

(3) 如果把这个注解添加方法上面,则只会影响该方法

@Service
@Transactional(propagation = Propagation.REQUIRED) //Spring默认行为
public class AccountService {

    @Autowired
    private AccountDao accountDao;


    public void accountMoney() {
        accountDao.reduceMoney();

        int number = 10 / 0;

        accountDao.addMoney();
    }
}
测试

此时开始事务后,测试上述转账过程中出现数据不一致的问题,经测试转账过程出现问题,事务回滚操作,数据没有变化。

声明式事务:基于XML方式

修改Spring配置文件
将Spring配置文件中去掉tx:annotation-driven标签,并添加配置:

  • 第一步 配置事务管理器
  • 第二步 配置通知
  • 第三步 配置切入点和切面
<!-- 引入外部properties -->
<context:property-placeholder location="jdbc.properties"/>
<!-- 开启组件扫描 -->
<context:component-scan base-package="cn.evan.spring5"/>
<!-- 创建数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <!--注入数据源-->
     <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事务通知-->
<tx:advice id="txadvice">
     <!--配置事务属性参数-->
     <tx:attributes>
        <!-- 设置在哪些方法上配置相关事务 -->
        <!-- tx:method标签:配置具体的事务方法 -->
        <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
        <!-- read-only属性:设置只读属性 -->
        <!-- rollback-for属性:设置回滚的异常 -->
        <!-- no-rollback-for属性:设置不回滚的异常 -->
        <!-- isolation属性:设置事务的隔离级别 -->
        <!-- timeout属性:设置事务的超时属性 -->
        <!-- propagation属性:设置事务的传播行为 -->
        <tx:method name="save*" read-only="false" rollbackfor="
java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="update*" read-only="false" rollbackfor="
java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="delete*" read-only="false" rollbackfor="
java.lang.Exception" propagation="REQUIRES_NEW"/>
     </tx:attributes>
</tx:advice>

<!-- 配置切入点和切面-->
<aop:config>
     <!--配置切入表达式-->
    <aop:pointcut id="pt" expression="execution(* 
                                 com.atguigu.spring5.service.UserService.*(..))"/>
     <!--配置事务通知切面-->
     <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

声明式事务:纯注解方式

创建配置类,使用配置类替代 xml 配置文件。

@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
     //创建数据库连接池
     @Bean
     public DruidDataSource getDruidDataSource() {
          DruidDataSource dataSource = new DruidDataSource();
          dataSource.setDriverClassName("com.mysql.jdbc.Driver");
         dataSource.setUrl("jdbc:mysql:///user_db");
         dataSource.setUsername("root");
         dataSource.setPassword("root");
         return dataSource;
     }
    
     //创建 JdbcTemplate 对象
     @Bean
     public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
         //到 ioc 容器中根据类型找到 dataSource
         JdbcTemplate jdbcTemplate = new JdbcTemplate();
         //注入 dataSource
         jdbcTemplate.setDataSource(dataSource);
         return jdbcTemplate;
     }
    
     //创建事务管理器
     @Bean
     public DataSourceTransactionManager 
         getDataSourceTransactionManager(DataSource dataSource) {
         DataSourceTransactionManager transactionManager = 
             			new DataSourceTransactionManager();
         transactionManager.setDataSource(dataSource);
         return transactionManager;
     }
}

标签:教程,读取,Spring,void,事务,回滚,dataSource,public
From: https://www.cnblogs.com/lisong0626/p/17986175

相关文章

  • SpringBoot:Springboot整合Mqtt并处理问题
    搭建mqtt服务Docker搭建MQTT服务:https://www.cnblogs.com/nhdlb/p/17960641项目结构这是我的项目结构,主要有两个模块base-modules(业务模块)、base-utils(工具模块)组成,其中base-mqtt服务为工具模块,用于提供给其他业务模块引用依赖的。base-mqtt模块pom.xml这里我的Sprin......
  • 无涯教程-Rust - 错误处理
    在Rust中,错误可以分为两大类,如下表所示。Name&描述UsageRecoverable可恢复的错误ResultenumUnRecoverable无法恢复的错误panicmacro与其他编程语言不同,Rust没有Exception异常,它返回可恢复错误的枚举Result<T,E>,如果程序遇到不可恢复的错误,则调用panic宏。Panic......
  • springBoot自定义参数注解
    springBoot自定义参数注解前置条件:新建一个springboot项目1.新建一个标记注解@Authimportjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/***@authorwa......
  • 【精品教程】如何查看iOS崩溃日志
    简介当一个应用程序崩溃,会产生一个崩溃报告(crashreport),并存储到设备中。崩溃报告描述了应用程序崩溃的条件,通常包含每个执行线程的完整回溯。查看崩溃报告可以帮助我们了解应用程序的崩溃情况,并尝试修复问题。符号化崩溃报告崩溃报告需要进行符号化(symbolicated),才能够进行分析......
  • 解决跨域问题的8种方法,含网关、Nginx和SpringBoot~
    跨域问题是浏览器为了保护用户的信息安全,实施了同源策略(Same-OriginPolicy),即只允许页面请求同源(相同协议、域名和端口)的资源,当JavaScript发起的请求跨越了同源策略,即请求的目标与当前页面的域名、端口、协议不一致时,浏览器会阻止请求的发送或接收。解决跨域问题方案跨域问题......
  • 教程|幻兽帕鲁服务器数据备份与恢复
    搭建幻兽帕鲁个人服务器,最近不少用户碰到内存不足、游戏坏档之类的问题。做好定时备份,才能轻松快速恢复游戏进度这里讲一下如何定时将服务器数据备份到腾讯云轻量对象存储服务,以及如何在有需要的时候进行数据恢复。服务器中间的数据迁移,也可以参考本指南,免去手动拷贝数据,直接使用......
  • 无涯教程-Rust - 元组(Tuple)
    元组是复合数据类型,标量类型只能存储一种类型的数据,如一个i32变量只能存储一个整数值。在复合类型中,我们可以存储多个值,并且可以是不同类型。元组的长度是固定的,一旦声明,它们就无法增长或缩小,元组索引从0开始。Tuple-语法//语法1lettuple_name:(data_type1,data_type2,d......
  • Vue2入门之超详细教程十六-过滤器
    Vue2入门之超详细教程十六-过滤器1、简介过滤器定义:对要显示的数据进行特点格式化后再显示(适用于一些简单逻辑的处理)语法:1.注册过滤器:Vue.filter(name,callback)或newVue(filters:{})2.使用过滤器:{{xxx|郭琪琪名}}或v-bind:属性="xxx|过滤器名称"备注:1.过......
  • 转载——Linux/Macos环境下使用 steamcommunity 302 教程
    原博:https://www.dogfight360.com/blog/2319/steamcommunity302后端使用caddy,在生成所有配置文件后可直接迁移到Linux/Macos环境下使用1.首先要在Windows环境/Wine下运行steamcommunity302并在设置里打勾需要开启的功能,然后正常启动服务 2.前往caddy/release页下载......
  • 「Java开发指南」MyEclipse如何支持Spring Scaffolding?(二)
    在上文中(点击这里回顾>>),主要为大家介绍了使用Spring的Scaffolding应用程序,本文将继续讲解CRUDScaffolding。MyEclipsev2023.1.2离线版下载MyEclipse技术交流群:742336981欢迎一起进群讨论2.CRUDScaffoldingScaffolding指的是MyEclipse广泛代码生成功能的超集,从生成一组特......