9、Spring_事务管理

news/2024/10/22 7:20:46/

六、Spring 事务管理

1.Spring 事务简介

  • 事务概述:保证数据库操作同时成功或者同时失败

  • Spring 事务的概述:在数据层保证数据库操作同时成功或者同时失败

2.转账案例分析

  • 转账肯定有一个业务方法:给转出用户减钱,给转入用户加钱
  • 要求:
    • 要么同时成功要么同时失败

2.1Spring 平台事务管理器

  • 提供规范接口

    public interface PlatformTransactionManager {TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;void commit(TransactionStatus var1) throws TransactionException;void rollback(TransactionStatus var1) throws TransactionException;
    }
    
  • 方法

    • void commit(TransactionStatus var1):用于提交事务
    • void rollback(TransactionStatus var1):用户事务回滚
  • 具体实现:DataSourceTransactionManager 来实现的,通过DataSource dataSource 以 JDBC 事务的方式来控制事务在这里插入图片描述

2.2转账案例分析

  • 业务分析

    • 在业务层需要保证事务的同时成功或者同时失败
    • 结果
      • 出现异常:张三转账给李四,比如中途出现问题,张三的钱和李四的钱应该不出现误差
      • 没有出现异常:张三转账给李四,没有出现问题,张三的钱正常减少,李四的钱正常增多
  • 提供service 方法

    public interface IAccountService {/*** 实现转账操作* @param srcId 转账人* @param deskId 接收人* @param money 转账金额*/public void transfer(Long srcId,Long deskId,int money);
    }
    
  • 下载插件的官方地址 https://plugins.jetbrains.com/

  • 提供service 实现方法

    @Service
    public class AccountServiceImpl implements IAccountService {//会使用到 mapper@Autowiredprivate AccountMapper mapper;public void transfer(Long srcId, Long deskId, int money) {mapper.outAccount(srcId,money);//转账扣钱mapper.inAccount(deskId,money);//接收转账钱}
    }
    
  • 提供 mapper 接口

    public interface AccountMapper {void outAccount(@Param("id") Long srcId, @Param("money") int money);void inAccount(@Param("id")Long deskId,@Param("money") int money);
    }
    
  • 提供 mapper.xml

    </update><update id="outAccount">update accountset money = money-#{money,jdbcType=INTEGER}where id = #{id,jdbcType=BIGINT}</update><update id="inAccount">update accountset money = money+#{money,jdbcType=INTEGER}where id = #{id,jdbcType=BIGINT}</update>
    
  • 测试

     @Testpublic void testMybatis(){AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);IAccountService bean = context.getBean(IAccountService.class);bean.transfer(1L,2L,500);}
    

3.基于注解的方式实现

3.1@EnableTransactionManagement

  • @EnableTransactionManagement:用于开启事务支持的,直接添加到spring 配置类

  • 说明

    名称@EnableTransactionManagement
    位置配置类上方
    作用设置当前spring环境支持事务
  • 修改配置类

    @Configuration
    @ComponentScan("cn.sycoder")
    @PropertySource("db.properties")
    @Import({JdbcConfig.class,MyBatisConfig.class})
    //开启事务支持
    @EnableTransactionManagement
    public class SpringConfig {
    }
    

3.2 配置事务管理,配置数据源

  • 如果不配置的话会报:找不到数据源错误
    在这里插入图片描述

  • PlatformTransactionManager

  • 代码

    public class JdbcConfig {@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Value("${jdbc.driverClassName}")private String driverClassName;@Value("${jdbc.url}")private String url;//配置连接池@Beanpublic DataSource dataSource(){DruidDataSource source = new DruidDataSource();source.setUsername(username);source.setPassword(password);source.setDriverClassName(driverClassName);source.setUrl(url);return source;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager manager = new DataSourceTransactionManager();manager.setDataSource(dataSource);return manager;}
    }
    

3.3 @Transactional

  • @Transactional:为业务添加事务的

  • 说明

    名称@Transactional
    位置业务层接口上方,或者实现类上方,或者具体业务方法上方
    作用为当前的业务方法添加事务支持
  • 修改业务层

    • 业务方法上添加

      @Transactional
      public void transfer(Long srcId, Long deskId, int money) {mapper.outAccount(srcId,money);//转账扣钱System.out.println(1/0);mapper.inAccount(deskId,money);//接收转账钱
      }
      
    • 业务类上添加

      @Service
      @Transactional
      public class AccountServiceImpl implements IAccountService {//会使用到 mapper@Autowiredprivate AccountMapper mapper;public void transfer(Long srcId, Long deskId, int money) {mapper.outAccount(srcId,money);//转账扣钱System.out.println(1/0);mapper.inAccount(deskId,money);//接收转账钱}
      }
      
    • 接口层添加

      @Transactional
      public interface IAccountService {/*** 实现转账操作* @param srcId 转账人* @param deskId 接收人* @param money 转账金额*/public void transfer(Long srcId,Long deskId,int money);
      }
      

    4.事务角色

  • 在没有开启Spring事务之前:两条语句分别开启两个事务 T1 和 T2

    • 如果同时成功,T1和T2都会正常提交
    • 如果T1正常,T2之前抛出异常,就会出现T1能够正常转账,但是T2收不到钱,因为不是同一个事务导致金钱异常
    public void transfer(Long srcId, Long deskId, int money) {mapper.outAccount(srcId,money);//转账扣钱System.out.println(1/0);mapper.inAccount(deskId,money);//接收转账钱
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KE12TXbZ-1693295276398)(picture/image-20221105210048391.png)]

  • 开启Spring 事务管理之后

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQEeOh60-1693295276399)(picture/image-20221105210911281.png)]

    • 在转账 transfer 方法上加入 @Transactional 注解之后,该方法会新建一个事务T
    • 把 mapper 中 outAccount 事务 T1 加入到 事务T中,把 mapper 中 inAccount 事务 T2 也加入到事务T中
    • 通过 @Transactional 注解统一了 transfer 方法的事务保证转账和入账方法变成同一事务操作

4.1事务管理员&事务协调员

  • 事务管理员:发起新事务,使用 @Transactional 注解开启事务
  • 事务协调员:加入新事务,保证多个事务变成同一事务下的操作

5.@Transactional 属性

5.1 readOnly

  • 概述:表示只读,没有写操作。可以通过这个属性告诉数据库我们没有写操作,从而数据库可以针对只读sql做优化操作

  • 使用

    @Transactional(readOnly = true)
    public Account selectById(Long id){return mapper.selectByPrimaryKey(id);
    }
    
  • 如果对于有写操作的使用这个属性,会报如下错误

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhqysmIG-1693295276402)(picture/image-20221105212707549.png)]

5.2 timeout

  • 超时概述:事务再执行的时候,由于某些原因卡住,长时间占用数据库资源。此时很可能程序sql有问题,希望撤销事务,能够让事务结束,释放资源,即超时回滚。

  • 默认值是-1.-1表示用不回滚,单位是秒

  • int timeout() default -1;
    
  • 使用

    @Transactional(readOnly = true,timeout = 1)public Account selectById(Long id){try {Thread.sleep(10000L);} catch (InterruptedException e) {e.printStackTrace();}return mapper.selectByPrimaryKey(id);}
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hamXB1vD-1693295276402)(picture/image-20221105213517098.png)]

5.3 rollbackFor&rollbackForClassName

  • 回滚概述:回滚策略,希望对于什么样的异常回顾

  • 注意:并不是所有的异常 Spring 都会回滚,Spring 只对 Error 异常和 RuntimeException 异常回滚

  • 使用

    @Transactional(rollbackFor = IOException.class)public void transfer(Long srcId, Long deskId, int money) throws IOException {mapper.outAccount(srcId,money);//转账扣钱if(true){throw new IOException("");}mapper.inAccount(deskId,money);//接收转账钱}
    
    @Transactional(rollbackForClassName = "IOException")public void transfer(Long srcId, Long deskId, int money) throws IOException {mapper.outAccount(srcId,money);//转账扣钱if(true){throw new IOException("");}mapper.inAccount(deskId,money);//接收转账钱}
    

5.4 noRollbackFor&noRollbackForClassName

  • 不会滚概述:出现这个异常不回滚

  • 使用

    @Transactional(noRollbackFor = ArithmeticException.class)public void transfer(Long srcId, Long deskId, int money) throws IOException {mapper.outAccount(srcId,money);//转账扣钱System.out.println(1/0);mapper.inAccount(deskId,money);//接收转账钱}
    
    @Transactional(noRollbackForClassName = "ArithmeticException")public void transfer(Long srcId, Long deskId, int money) throws IOException {mapper.outAccount(srcId,money);//转账扣钱System.out.println(1/0);mapper.inAccount(deskId,money);//接收转账钱}
    

5.5 isolation

  • 概述:设置事务隔离级别;

  • 如果不记得事务隔离级别,回去复习一下我讲的MySql

    • DEFAULT :默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED : 读未提交
    • READ_COMMITTED : 读已提交
    • REPEATABLE_READ : 重复读取
    • SERIALIZABLE: 串行化
  • 使用

    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public Account selectById(Long id) throws IOException {return mapper.selectByPrimaryKey(id);
    }
    

5.6propagation

  • 事务传播行为:事务协调员对事务管理员所携带的事务的处理态度

  • 说明

    传播属性说明
    REQUIRED外围方法会开启新事务,内部方法会加入到外部方法的事务中
    SUPPORTS外围方法没有事务,则内部方法不执行事务
    MANDATORY使用当前事务,如果当前没有事务就抛异常
    REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起
    NOT_SUPPORTED不支持事务
    NEVER不支持事务,如果存在事务还会抛异常
    NESTED如果当前存在事务,则在嵌套事务内执行,如果不存在,执行REQUIRED类似操作
  • 实操

    • REQUIRED:T1和T2会加入T中

      @Transactional(propagation = Propagation.REQUIRED)//事务T
      public void transfer(Long srcId, Long deskId, int money) throws IOException {mapper.outAccount(srcId,money);//转账扣钱 //事务T1System.out.println(1/0);mapper.inAccount(deskId,money);//接收转账钱 //事务T2
      }
      
    • SUPPORTS:外围没事务,所以内部只执行自己的事务,T1 和 T2 单独执行

      public void transfer(Long srcId, Long deskId, int money) throws IOException {mapper.outAccount(srcId,money);//转账扣钱//事务T1System.out.println(1/0);mapper.inAccount(deskId,money);//接收转账钱//事务T2}
      
    • REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起

       @Transactional(propagation=Propagation.REQUIRES_NEW)public void outAccount(Long id,  int money){mapper.outAccount(id,money);//转账扣钱}@Transactional(propagation=Propagation.REQUIRES_NEW)public void inAccount(Long id,  int money){mapper.inAccount(id,money);//转账扣钱}public void transfer(Long srcId, Long deskId, int money) throws IOException {outAccount(srcId,money);inAccount(deskId,money);throw new RuntimeException();}
      
      • 这种情况上面一条语句能够正常执行
      @Transactional(propagation = Propagation.REQUIRES_NEW)public void outAccount(Long id, int money) {mapper.outAccount(id, money);//转账扣钱}@Transactional(propagation = Propagation.REQUIRES_NEW)public void inAccount(Long id, int money) {if (true)throw new RuntimeException();mapper.inAccount(id, money);//转账扣钱}
      

6.基于XML事务

  • 导入依赖

    <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.17.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.17.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><!--            <scope>test</scope>--></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.29</version></dependency><!--        spring 整合 mybatis 的包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version></dependency><!--        mybatis 包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><!--        spring 操作 jdbc 包--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>4.3.29.RELEASE</version></dependency></dependencies>
    
  • 配置文件

    <?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:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><bean id="accountService" class="cn.sycoder.service.impl.AccountServiceImpl">
    <!--        <property name="mapper" ref="mapper"/>--></bean><!--    <bean id="mapper" class="cn.sycoder.mapper.AccountMapper"></bean>--><aop:config><aop:advisor advice-ref="tx" pointcut="execution(* cn.sycoder.service.impl.*.*(..))"></aop:advisor></aop:config><tx:advice id="tx" transaction-manager="txManager"><tx:attributes><tx:method name="get*" read-only="true"/></tx:attributes></tx:advice><bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!-- (this dependency is defined somewhere else) --><property name="dataSource" ref="dataSource"/></bean><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><context:property-placeholder location="db.properties"/></beans>
    
  • 配置详解

    • 引入db.properties

      <context:property-placeholder location="db.properties"/>
      
    • 配置连接池

      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/>
      </bean>
      
    • 配置事务管理器

      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
      </bean>
      
    • 配置 aop 事务增强

      <aop:config><aop:advisor advice-ref="tx" pointcut="execution(* cn.sycoder.service.impl.*.*(..))"/>
      </aop:config><tx:advice id="tx" transaction-manager="txManager"><tx:attributes><tx:method name="get*" read-only="true"/></tx:attributes>
      </tx:advice>    
      
  • 注意:如果你还想通过 xml 配置 mybatis ,那么你还需要把 mybatis 配置文件搞一份过来,通过xml 配置好 mybatis 之后,然后再获取 sqlSessionFactory 去获取 mapper 文件

  • 注意:spring 是面试重头戏,所以,你需要花时间认真巩固和复习,ioc 和 di 特别是对于常用注解,以及事务机制,aop 等都很爱问。


http://www.ppmy.cn/news/1072671.html

相关文章

做区块链卡牌游戏有什么好处?

区块链卡牌游戏是一种基于区块链技术的创新性游戏形式&#xff0c;它将传统的卡牌游戏与区块链技术相结合&#xff0c;实现了去中心化、数字化资产的交易和收集。这种新型游戏形式正逐渐在游戏行业引起了广泛的关注和热潮。本文将深入探讨区块链卡牌游戏的定义、特点以及其在未…

Linux中的dpkg指令(dpkg -l | grep XXX等)

dpkg是Debian包管理系统中的一个工具&#xff0c;用于在Linux系统中安装、升级、删除和管理软件包。它是Debian、Ubuntu以及基于它们的发行版中的包管理器。 dpkg 有很多用法&#xff0c;常用之举例:dpkg -l | grep apt 显示系统中安装的与apt相关&#xff08;命名&#xff09…

恒运资本:华为Mate 60 Pro突然发售拉动半导体股,中芯国际等开盘涨超5%

8月30日&#xff0c;受华为突然发售Mate 60 Pro手机影响&#xff0c;A股开盘后半导体板块迅速拉升&#xff0c;伟测科技、美芯晟涨超10%&#xff0c;利扬芯片、唯捷创芯、芯动联科、中芯世界、华虹公司等涨超5%。 恒运资本平台&#xff08;百度搜索恒运资本&#xff09;是深圳…

Java 8 新特性——Lambda 表达式(2)

一、Java Stream API Java Stream函数式编程接口最初在Java 8中引入&#xff0c;并且与 lambda 一起成为Java开发里程碑式的功能特性&#xff0c;它极大的方便了开放人员处理集合类数据的效率。 Java Stream就是一个数据流经的管道&#xff0c;并且在管道中对数据进行操作&…

Python Opencv实践 - Canny边缘检测

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_GRAYSCALE) print(img.shape)#图像Canny边缘检测 #cv.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradien…

python面试:使用cProfile剖析程序性能

我们需要安装tuna&#xff1a;pip install tuna 程序执行完毕后&#xff0c;我们会得到一个results.prof&#xff0c;在CMD中输入指令&#xff1a;“tuna results.prof”。 import time import cProfile import pstatsdef add(x, y):resulting_sum 0resulting_sum xresulti…

2023-08-29 LeetCode(带因子的二叉树)

2023-08-29每日一题 一、题目编号 823. 带因子的二叉树二、题目链接 点击跳转到题目位置 三、题目描述 给出一个含有不重复整数元素的数组 arr &#xff0c;每个整数 arr[i] 均大于 1。 用这些整数来构建二叉树&#xff0c;每个整数可以使用任意次数。其中&#xff1a;每…

flink源码分析-获取最大可以打开的文件句柄

flink版本: flink-1.11.2 代码位置: org.apache.flink.runtime.util.EnvironmentInformation 调用位置: taskmanager启动类: org.apache.flink.runtime.taskexecutor.TaskManagerRunner long maxOpenFileHandles EnvironmentInformation.getOpenFileHandlesLimit(); …