首页 > 其他分享 >学习笔记-Spring事务

学习笔记-Spring事务

时间:2023-05-23 11:35:01浏览次数:45  
标签:回滚 Spring void 事务 笔记 dataSource public user

学习的文章

小姐姐非要问我:spring编程式事务是啥? (qq.com)

一文搞懂什么是事务 - 知乎 (zhihu.com)

阿里3面:Spring声明式事务连环炮,让我措手不及。。 (qq.com)

带你读懂Spring 事务——事务的传播机制 - 知乎 (zhihu.com)

spring 事务失效的 12 种场景_事务什么时候失效_hanjq_code的博客-CSDN博客

什么是事务

  • 事务是并发操作的单位

  • 是用户定义的操作序列

  • 事务如果成功,则会提交

  • 事务如果失败,则会回滚

事务的四大特性

  • 原子性

    • 事务中操作,要么不做,要么都做
  • 持久性

    • 一个事务一旦提交,它对数据库的改变是永久的
  • 一致性

    • 事务让数据库从一个一致性状态转移到另一个一致性状态

    • 比如

      • 事务前,A有50,B有50,总共有100

      • 事务中,A送给B20

      • 事务后,A有30,B有70,总共还是有100

  • 隔离性

    • 一个事务的执行不能被其他事务所干扰

    • 分成不同的等级

事务并发访问导致的数据问题

脏读

读到了修改但还没有提交的数据

  • A事务修改了某条记录的字段c,但还没有提交

  • B事务在此时读取了字段c

  • A事务发生了回滚,字段c恢复了修改前的状态

  • 但B事务持有的还是修改后的状态

不可重复读

某个事务在执行过程中,两次读同一个条记录,但结果不一样

  • A事务读取了记录c,值为10

  • B事务修改了记录c,值为0

  • A事务再次读取记录c,值为0

幻读

某个事务在执行过程中,前后两次读取记录,数据总量不一样

  • A事务统计了表c中的记录总数,结果为10

  • B事务删除了表c中的5条记录

  • A事务再次统计了表c中的记录总数,结果为5

和不可重复读的区别在于,幻读针对的是记录条数,不可重复读针对的是记录内容

事务的隔离级别

针对事务并发访问时出现的问题,设置了四种事务的隔离级别

读未提交

可以的读取还没有提交的数据

没有限制,三种问题都有可能发生

读已提交

只能读取已经提交了的数据

不会发生脏读,但是会发生不可重复读和幻读

可重复读

一个事务前后读取的同一条记录的结果必须一致

不会发生脏读和不可重复读,但会发生幻读

mysql中默认的事务隔离级别

串行化

所有的事务必须依次执行

不会发生脏读、不可重读读和幻读

效率比较低

Spring事务的使用方法

Spring分为两种控制事务的方法

  • 编程式事务

    • 方法1:通过PlatformTransactionManager控制事务

    • 方法2:通过TransactionTemplate控制事务

  • 声明式事务

    • 常用

编程式事务的使用

使用PlatformTransactionManager

@Test
public void test1() throws Exception {
    //定义一个数据源
    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);
    //定义一个JdbcTemplate,用来方便执行数据库增删改查
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    //1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
    PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
    //2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
    TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    //3.开启事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象
    TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
    //4.执行业务操作,下面就执行2个插入操作
    try {
        System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
        jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
        jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
        //5.提交事务:platformTransactionManager.commit
        platformTransactionManager.commit(transactionStatus);
    } catch (Exception e) {
        //6.回滚事务:platformTransactionManager.rollback
        platformTransactionManager.rollback(transactionStatus);
    }
    System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

使用TransactionTemplate

@Test
public void test1() throws Exception {
    //定义一个数据源
    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);
    //定义一个JdbcTemplate,用来方便执行数据库增删改查
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    //1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
    PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
    //2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    transactionDefinition.setTimeout(10);//如:设置超时时间10s
    //3.创建TransactionTemplate对象
    TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
    /**
     * 4.通过TransactionTemplate提供的方法执行业务操作
     * 主要有2个方法:
     * (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
     * (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
     * 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
     * 那么什么时候事务会回滚,有2种方式:
     * (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
     * (2)execute方法或者executeWithoutResult方法内部抛出异常
     * 什么时候事务会提交?
     * 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
     */
    transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
        @Override
        public void accept(TransactionStatus transactionStatus) {
            jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
            jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");

        }
    });
    System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

声明式事务的使用

  • 在配置类上使用@EnableTransactionManagement

    • Springboot可以加在启动类上
  • 定义事务管理器

    • @Bean
      public PlatformTransactionManager transactionManager(DataSource dataSource) {
          return new DataSourceTransactionManager(dataSource);
      }
      
    • springboot中有默认的事务管理器

  • 在需要事务的目标上加上@Transaction注解

    • 作用位置

      • 注意:@Transaction只对public方法有效

      • @Transacion放在接口上,接口的所有实现类中所有的public方法都自动加上事务

      • @Transaction放在类上,当前类以及其下无限级子类中的public方法都被加上事务

      • @Transaction放在public方法上,方法被加上事务

    • 属性

      • "transactionManager"或"value"

        • 指定事务管理器的bean对象

        • 为空的话,默认按类型获取

      • “propagation”

        • 指定事务的传播类型

        • 默认为REQUIRED

      • “rollbackFor”

        • 自定义回滚异常

事务的传播类型

  • REQUIRED

    • 如果当前有事务,则加入当前事务

    • 如果当前没有事务,则自己新建一个事务

  • SUPPORTS

    • 如果当前有事务,则加入当前事务

    • 如果当前没有事务,则以非事务方式执行

  • MANDATORY

    • 如果当前有事务,则加入当前事务

    • 如果当前没有事务,则抛出异常

  • REQUIERES_NEW

    • 如果当前有事务,则将该事务挂起,另外新创建一个事务

    • 如果当前没有事务,则新创建一个事务

  • NOT_SUPPORTED

    • 如果当前有事务,则将该事务挂起,以非事务方式执行

    • 如果当前没有事务,则以非事务方式执行

  • NEVER

    • 如果当前有事务,则抛出异常

    • 如果当前没有事务,以非事务方式执行

  • NESTED

    • 如果当前有事务,则在当前事务中嵌套一个事务执行

    • 如果当前没有事务,则新建一个事务执行

事务失效或回滚异常的12种情况

事务失效

  • 访问权限问题

    • 事务只能对public的方法方法生效
  • 方法用final或static修饰

    • spring事务是使用动态代理的方式实现的

    • 如果加了final或public方法,则方法无法被代理

  • 方法内部调用

    • @Service
      public class UserService {
       
          @Autowired
          private UserMapper userMapper;
       
        
          public void add(UserModel userModel) {
              userMapper.insertUser(userModel);
              updateStatus(userModel);
          }
       
          @Transactional
          public void updateStatus(UserModel userModel) {
              doSameThing();
          }
      }
      
    • 在add方法中是通过this来调用updateStatus方法的,没有通过代理

    • 解决方法:

      • 1.注入自己

        • @Servcie
          public class ServiceA {
             @Autowired
             prvate ServiceA serviceA;
           
             public void save(User user) {
                   queryData1();
                   queryData2();
                   serviceA.doSave(user);
             }
           
             @Transactional(rollbackFor=Exception.class)
             public void doSave(User user) {
                 addData1();
                 updateData2();
              }
           }
          
      • 2.通过AopContext.currentProxy()获取代理对象

        • @Servcie
          public class ServiceA {
           
             public void save(User user) {
                   queryData1();
                   queryData2();
                   ((ServiceA)AopContext.currentProxy()).doSave(user);
             }
           
             @Transactional(rollbackFor=Exception.class)
             public void doSave(User user) {
                 addData1();
                 updateData2();
              }
           }
          
  • 未被spring管理

    • 忘了给类添加注释了

    • 没有被放到IOC容器中

  • 多线程调用

    • spring事务是通过数据库连接来实现的

    • 在多线程中,每一个线程用的都是不同的数据库连接

  • 表不支持事务

    • MyISAM引擎的表不支持事务
  • 未开启事务

    • springboot需要在启动类上加上@EnableTransactionManagement的注解

    • 传统spring项目需要在applicationContext.xml中进行相关的配置

事务回滚异常

  • 使用了错误的传播特性

  • 手动捕获了异常

    • 如果想要spring事务能够正常回滚,必须抛出它能够处理的异常

    • 使用try/catch将异常捕获,将导致事务不会回滚

  • 抛得异常类型不正确

    • spring事务,默认情况下只会回滚RuntimeException或Error
  • 自定义回滚异常不匹配

    • 比如定义了BusinessException,但抛出的是SqlException
  • 嵌套事务回滚范围多了

    • public class UserService {
       
          @Autowired
          private UserMapper userMapper;
       
          @Autowired
          private RoleService roleService;
       
          @Transactional
          public void add(UserModel userModel) throws Exception {
              userMapper.insertUser(userModel);
              roleService.doOtherThing();
          }
      }
       
      @Service
      public class RoleService {
       
          @Transactional(propagation = Propagation.NESTED)
          public void doOtherThing() {
              System.out.println("保存role表数据");
          }
      }
      
    • roleService.doOtherThing()如果发生回滚,它抛出的异常没有被处理,会继续往上级抛

    • add()在捕获到向上抛的异常后也会发生回滚

    • 应该手动进行捕获

      • @Slf4j
        @Service
        public class UserService {
         
            @Autowired
            private UserMapper userMapper;
         
            @Autowired
            private RoleService roleService;
         
            @Transactional
            public void add(UserModel userModel) throws Exception {
         
                userMapper.insertUser(userModel);
                try {
                    roleService.doOtherThing();
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        

标签:回滚,Spring,void,事务,笔记,dataSource,public,user
From: https://www.cnblogs.com/Andl-Liu/p/17422805.html

相关文章

  • SpringBoot声明连接多个redis数据源配置模版
    在实际开发中,我们可能会用到2个不同的redis数据源;如何连接查询详情:文章目录一、依赖二、配置文件三、config类配置四、序列化问题五、封装工具类一、依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-dat......
  • (二)Spring源码解析:默认标签解析
    一、概述还记得我们在上一讲末尾提到的关于默认标签解析和自定义标签解析吧。本讲就来针对默认标签解析进行讲解。为了便于衔接上一讲的内容,我们将源码部分粘贴出来:从上图中的源码中,我们可以看出默认标签的解析是在parseDefaultElement(ele,delegate)方法中实现的。我们来看一下这......
  • (三)Spring源码解析:自定义标签解析
    一、使用示例步骤1:创建User实体步骤2:定义一个XSD文件描述组件内容步骤3:创建BeanDefinitionParser接口的实现类,用来解析XSD文件中的定义和组件定义。步骤4:创建NamespaceHandlerSupport实现类,目的是将组件注册到Spring容器中。步骤5:编写spring.handlers和spring.schemas文件,默认位置......
  • 如何利用Redis进行事务处理呢?
    一、概述事务的本质,其实就是一组命令的集合。一个事务中的所有命令都会按照命令的顺序去执行,而中间不会被其他命令加塞。Redis提供了事务相关的5个指令,分别是:DISCARD、EXEC、MULTI、UNWATCH和WATCH。如下图所示:下面我们就对Redis的事务操作一一的进行介绍。二、MULTI(v1.2.0)指令格式......
  • Unity3D高级编程主程手记 学习笔记二:C#技术要点
    1.Untiy3D中C#的底层原理Unity底层在运行C#程序时有两种机制:一种是Mono,另一种是IL2CPP。Mono存在的目的是为了跨平台,因为最初C#只支持Windows。而IL可以看成是一种汇编语言且完全基于堆栈,必须运行在虚拟机上。也就是说C#会被编译器编译成IL,当需要他们时就会被实时的加载到运行库......
  • Java开发笔记之将一个List拷贝到另一个List的问题
    0x00概述在对List数据进行不同的数据操作的时候,例如分支1将List按照A来排序,分支2将List按照B来排序,需要将List进行数据层面的拷贝; 0x01错误的操作仅仅是List的引用,并没拷贝List内的数据进行处理List<String>list1=newArrayList<>();List<String>list2=newArrayL......
  • pandas 笔记
    1.pandas两列取最小值,如果其中一列为nan,会取另外一列>>>df3abc0111.01112.02112.0322NaN432NaN>>>df3['d']=df3[['a','c']].min(axis=1)>>>df3abcd011......
  • 聊聊如何利用spring插件来实现策略模式
    前言偶然的机会发现spring有个spring-plugin,官网对它的介绍是SpringPluginprovidesamorepragmaticapproachtoplugindevelopmentbyprovidingthecoreflexibilityofhavingpluginimplementationsextendingacoresystem'sfunctionalitybutofcoursenotdel......
  • 【xhs】笔记更新监控,x-s、x-t、a1、web_session
    本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰,更不会影响计算机信息系统的正常运行。不得将代码用于非法用途,如侵立删!标题环境win10、macPython3.9node.jsv4.16x-s、x-t、a1、web_session获取博主详细信息defget_use......
  • GitlabCI学习笔记之一:安装Gitlab和GitLabRunner
    1.安装GitLab#下载地址https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/wgethttps://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-12.9.0-ce.0.el7.x86_64.rpmrpm-ivhgitlab-ce-12.9.0-ce.0.el7.x86_64.rpmvim/etc/gitlab/gitlab.rbexterna......