• 事务详细介绍


     目录

    一、什么是事务?🌹

    二、为什么需要事务? ⭐

    1、事务的特性 

    三、事务隔离级别(Isolation ) 🌙

    四、事务传播行为(Propagation)🍫

    五、Spring声明式事务 🍓

    1、什么是声明式事务?

    声明式事务的2种实现方式

    xml的方式:

    ①  导入相关的Manven依赖

    ②  引入相关的头部信息  (只需要tx的)

    ③  编写配置

    ④  编写代码(实体类、Mapper、service层......)

    注解的方式:

            XML 方式开启事务的注解支持

           注解方式开启事务的注解支持

    六、并发事务所产生的一些问题  🐻

    1、脏读

    2、不可重复读

    3、幻读


    一、什么是事务?🌹

            事务(Transaction)是指将一系列数据操作捆绑成为一个整体进行统一管理。如果某一事物执行成功,则该事物中进行的所有数据更改均会提交,成为数据库中的永久组成部分。如果事物执行时遇到错误且必须取消回滚,则数据将全部恢复到操作前的状态,所有数据的更改均被清除。通俗来说可以理解为(多个操作看作一个整体、要么都执行要么都不执行)。

    二、为什么需要事务? ⭐

    比如说转账,张三需要给李四转账500。那么张三账户上面的金额是不是要减500,而李四的账户上要加500。那万一李四的账户上没有收到这500,而张三的账户上已经减了500。这是不是就有问题了。该怎么解决呢,就要用到我们的事物了!

    1、事务的特性 

    • 简称ACID 属性
    原子性 ( Atomicity )

    事务是一个完整的操作,事务的各步操作是不可分的(原子的),要么都执行,要么都不执行

    一致性 ( Consistency )

    当事务完成时,数据必须处于一致状态

    隔离性 ( Isolation )

    并发事务之间彼此隔离、独立,不应以任何方式依赖于或影响其他事务

    持久性 ( Durability )

    事务完成后,它对数据库的修改被永久保持

    三、事务隔离级别(Isolation ) 🌙

    • 事务隔离级别指的是若干个并发的事务之间的隔离程度,一般可分为以下五个级别:
    DEFAULT默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这个值就是READ_COMMITTED。
    READ_UNCOMMITTED表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。
    READ_COMMITTED表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
    REPEATABLE_READ表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
    SERIALIZABLE所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

    四、事务传播行为(Propagation)🍫

            所谓事务传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时指定的一个事务性方法的执行行为。下面举个例子进行说明:

    1. @Transactional
    2. public void test1() {
    3. test2(); // test1() 调用 test2()
    4. }
    5. @Transactional(propagation = xxx)
    6. public void test2() {
    7. // 在开始当前事务之前,也就是 test2 事务之前,调用者 test1 已经存在事务管理,
    8. // 此时使用 propagation 属性指定 test2 事务的执行行为
    9. }

    一般可分为以下六种行为:

    REQUIRED默认值。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    REQUIRES_NEW创建一个新的事务,如果当前存在事务,则把当前事务挂起。
    SUPPORTS如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    NEVER以非事务方式运行,如果当前存在事务,则抛出异常。
    MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED。

    五、Spring声明式事务 🍓

    1、什么是声明式事务?

             所谓声明式事务,就是通过配置的方式,比如通过配置文件(xml)或者注解的方式,告诉spring,哪些方法需要spring帮忙管理事务,然后开发者只用关注业务代码,而事务的事情spring自动帮我们控制。

    声明式事务的2种实现方式

    ①   配置文件的方式,即在spring的xml文件中进行统一配置,开发者基本上就不用关注事务的事情了,代码中无需关心任何和事务相关的代码,一切交给spring处理。

    ②  注解的方式,只需在需要spring来帮忙管理事务的方法上加上@Transactional注解就可以了,注解的方式相对来说更简洁一些。

    xml的方式:
    ①  导入相关的Manven依赖
    1. <dependency>
    2. <groupId>org.springframeworkgroupId>
    3. <artifactId>spring-txartifactId>
    4. <version>5.2.2.RELEASEversion>
    5. dependency>

    ②  引入相关的头部信息  (只需要tx的)
    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:tx="http://www.springframework.org/schema/tx"
    5. xmlns:context="http://www.springframework.org/schema/context"
    6. xmlns:aop="http://www.springframework.org/schema/aop"
    7. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    8. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    9. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd ">
    ③  编写配置
    1. <tx:advice id="advice" transaction-manager="transactionManager">
    2. <tx:attributes>
    3. <tx:method name="insert*" rollback-for="java.lang.Exception"/>
    4. <tx:method name="delete*" rollback-for="java.lang.Exception"/>
    5. <tx:method name="update*" rollback-for="java.lang.Exception"/>
    6. tx:attributes>
    7. tx:advice>
    8. <aop:config>
    9. <aop:pointcut id="txPointcut" expression="execution (* com.cskt.service.impl.*ServiceImpl.*(..))"/>
    10. <aop:advisor advice-ref="advice" pointcut-ref="txPointcut"/>
    11. aop:config>
    ④  编写代码(实体类、Mapper、service层......)
    1. package com.cskt.domain;
    2. import lombok.Data;
    3. import java.math.BigDecimal;
    4. /**
    5. * @Description TODO
    6. * @Author Yii_oo
    7. * @Date 2023-08-18 16:17
    8. * @Version 1.0
    9. * @ClassDesc 银行卡实体类
    10. */
    11. @Data
    12. public class Card {
    13. private int id;
    14. private String name;
    15. private BigDecimal money;
    16. }
    1. package com.cskt.mapper;
    2. import com.cskt.domain.Card;
    3. import org.apache.ibatis.annotations.Options;
    4. import org.apache.ibatis.annotations.Select;
    5. import org.apache.ibatis.annotations.Update;
    6. import org.springframework.stereotype.Repository;
    7. @Repository
    8. public interface CardMapper {
    9. /**
    10. * 转账的方法
    11. * @param card
    12. * @return
    13. */
    14. @Update("UPDATE card SET money = #{money} WHERE id = #{id};")//使用注解的方式
    15. @Options(useGeneratedKeys = true , keyProperty = "id")//useGeneratedKeys 设置为 true,表示启用数据库自动生成主键。
    16. int transferAccount(Card card); //keyProperty 指定了将生成的主键值赋值给 User 对象的 id 属性。
    17. /**
    18. * 根据id查询
    19. * @return
    20. */
    21. @Select("select * from card where id=#{id}")
    22. @Options(useGeneratedKeys = true , keyProperty = "id")
    23. Card selectById(int id);
    24. }
    1. package com.cskt.service;
    2. import com.cskt.domain.Card;
    3. public interface CardService {
    4. /**
    5. * 根据id查询
    6. * @param id
    7. * @return
    8. */
    9. Card selectById(int id);
    10. int transferAccount(Card card);
    11. int transferAccount2(Card card);
    12. }
    1. package com.cskt.service.impl;
    2. import com.cskt.domain.Card;
    3. import com.cskt.mapper.CardMapper;
    4. import com.cskt.service.CardService;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.beans.factory.annotation.Qualifier;
    7. import org.springframework.stereotype.Service;
    8. /**
    9. * @Description TODO
    10. * @Author Yii_oo
    11. * @Date 2023-08-18 17:08
    12. * @Version 1.0
    13. */
    14. @Service("cardService")
    15. public class CardServiceImpl implements CardService {
    16. @Autowired
    17. @Qualifier("cardMapper")
    18. private CardMapper cardMapper;
    19. @Override
    20. public Card selectById(int id) {
    21. Card card = cardMapper.selectById(id);
    22. return card;
    23. }
    24. @Override
    25. public int transferAccount(Card card) {
    26. return cardMapper.transferAccount(card);
    27. }
    28. @Override
    29. public int transferAccount2(Card card) {
    30. int count = cardMapper.transferAccount(card);
    31. int i= 1/0; //模拟转账失败
    32. return count;
    33. }
    34. }
    1. package com.cskt.service.impl;
    2. import com.cskt.domain.Card;
    3. import com.cskt.service.CardService;
    4. import org.junit.Test;
    5. import org.springframework.context.ApplicationContext;
    6. import org.springframework.context.support.ClassPathXmlApplicationContext;
    7. import java.math.BigDecimal;
    8. import static org.junit.Assert.*;
    9. public class CardServiceImplTest {
    10. private ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    11. private CardService cardService =(CardService) context.getBean("cardService");
    12. @Test
    13. public void TestTransferAccounts(){
    14. Card card=cardService.selectById(1);
    15. card.setMoney(card.getMoney().subtract(new BigDecimal(1000)));
    16. int count = cardService.transferAccount(card);
    17. System.out.println("执行第一次转账:-----"+count+"---------");
    18. Card card2=cardService.selectById(2);
    19. card2.setMoney(card2.getMoney().add(new BigDecimal(1000)));
    20. int count2 = cardService.transferAccount2(card2);
    21. System.out.println("执行第二次转账:-----"+count2+"---------");
    22. }
    23. }

     

     表没被修改,这样就成功了!当我们把模拟报错给删掉,让他正常执行

     

    注解的方式:
    • 使用注解方式的前提是开启事务的注解支持,有两种方式配置:
            XML 方式开启事务的注解支持
    1. <tx:annotation-driven transaction-manager="transactionManager"/>
           注解方式开启事务的注解支持
    1. // 其它注解
    2. @EnableTransactionManagement // 开启事务的注解支持,会自动加载事务管理器
    3. public class SpringConfig {
    4. // 注册各种 bean
    5. }

    开启事务的注解支持之后,可以不用使用 XML,而是直接将 @Transactional 注解作用在类或者方法上。如下所示:

    1. @Transactional(rollbackFor=RuntimeException.class) //如果方法抛出了RuntimeException或其子类的异常,事务将被回滚.
    2. @Override
    3. public Card selectById(int id) {
    4. Card card = cardMapper.selectById(id);
    5. int i=1/0;
    6. return card;
    7. }

    六、并发事务所产生的一些问题  🐻

    • 具体来说就是:脏读不可重复读幻读

    1、脏读

          脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,可能最终不会存到数据库中,也就是不存在的数据。读到了不一定存在的数据,这就是脏读。

            脏读最大的问题就是可能会读到不存在的数据。比如在上图中,事务B的更新数据被事务A读取,但是事务B回滚了,更新数据全部还原,也就是说事务A刚刚读到的数据并没有存在于数据库中。

    从宏观来看,就是事务A读出了一条不存在的数据,这个问题是很严重的。

    2、不可重复读

           不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。 

           不可重复读即当事务 A 按照查询条件得到了一个结果集,这时事务 B 对事务 A 查询的结果集数据做了修改操作,之后事务 A 为了数据校验继续按照之前的查询条件得到的结果集与前一次查询不同,导致不可重复读取原始数据。

    3、幻读

           幻读是指当事务 A 按照查询条件得到了一个结果集,这时事务 B 对事务 A 查询的结果集数据做新增操作,之后事务 A 继续按照之前的查询条件得到的结果集平白无故多了几条数据,好像出现了幻觉一样。

    以上面表为例子:

    1、事务A,查询是否存在 id=3 的记录,没有则插入,这是我们期望的正常业务逻辑。

    2、这个时候 事务B 新增的一条 id=3 的记录,并提交事务。

    3、事务A,再去查询 id=3 的时候,发现还是没有记录(因为这里是在RR级别下研究(可重复读),所以读到依然没有数据)

    4、事务A,插入一条 id=3 的数据。

    最终 事务A 提交事务,发现报错了。这就很奇怪,查的时候明明没有这条记录,但插入的时候 却告诉我 主键冲突,这就好像幻觉一样。这才是所有的幻读。

    不可重复读侧重表达 读-读,幻读则是说 读-写,用写来证实读的是鬼影。

    上述所说的"脏读",“不可重复读”,"幻读"这些问题,其实就是数据库读一致性问题,必须由数据库提供的事务隔离机制来进行解决。

  • 相关阅读:
    mysql和sqlserve中smallint存储什么类型数据?
    Databend 开源周报 #66
    NSSCTF做题(9)
    Leetcode 503.下一个更大元素Ⅱ
    Apollo planning之hybrid A*
    阿里巴巴算力攻坚新突破:阿里云20%新增算力将使用自研CPU
    使用jmeter进行接口测试
    redis: 记录一次线上redis内存占用过大问题解决过程
    基于开源IM即时通讯框架MobileIMSDK:RainbowChat v11.5版已发布
    Leetcode 1239. 串联字符串的最大长度
  • 原文地址:https://blog.csdn.net/2301_78163113/article/details/132636084