【Spring】Spring中的事务

文章目录

  • 1. Spring事务简介
  • 2. Spring事务的案例
    • 案例代码
      • 代码目录结构
      • 数据库
      • pom.xml
      • Resource/jdbc.properties
      • config/SpringConfig.java
      • config/JdbcConfig.java
      • config/MyBatisConfig.java
      • dao/AccountDao.java
      • service/AccountService.java
      • service/impl/AccountServiceImpl.java
      • 测试方法
    • 问题分析
    • 事务管理三步
      • 第一步:在业务层接口上加上注解@Transactional
      • 第二步:在JdbcConfig.java中注册事务管理器
      • 第三步:在SpringConfig.java上加上开启事务管理的注解@EnableTransactionManagement
  • 3. Spring事务角色
  • 4. Spring事务属性
    • 事务配置
    • 案例:转账业务追加日志
      • 案例代码
        • 代码结构
        • 数据库表
        • dao/LogDao.java
        • service/LogService.java
        • service/LogServiceImpl.java
        • 修改service/impl/AccountServiceImpl.java如下
      • 改进
    • 事务传播行为

1. Spring事务简介

事务作用: 在数据层保障一系列的数据库操作同成功、同失败
Spring事务作用: 在数据层或业务层保障一系列的数据库操作同成功、同失败
Spring为事务提供的接口和实现类:

// 接口
public interface PlatformTransactionManager{void commit(TransactionStatus status) throws TransactionException;void rollback(TransactionStatus status) throws TransactionException;
}
// 实现类
public class DataSourceTransactionManager{...
}

2. Spring事务的案例

需求: 实现两个账户间的转账操作
需求微缩: A账户减钱,B账户加钱
分析:
在这里插入图片描述

案例代码

代码目录结构

在这里插入图片描述

数据库

在这里插入图片描述

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>project5</artifactId><version>0.0.1-SNAPSHOT</version><name>project5</name><description>project5</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.3</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.3</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.1</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.13</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.25</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency></dependencies></project>

Resource/jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=123456

config/SpringConfig.java

package com.example.project5.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;@Configuration
@PropertySource("classpath:jdbc.properties")
@ComponentScan("com.example.project5")
@Import({JdbcConfig.class, MyBatisConfig.class})
public class SpringConfig {
}

config/JdbcConfig.java

package com.example.project5.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;public class JdbcConfig {@Value("${jdbc.driver}")String driver;@Value("${jdbc.url}")String url;@Value("${jdbc.username}")String username;@Value("${jdbc.password}")String password;@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUsername(username);ds.setPassword(password);ds.setUrl(url);return ds;}}

config/MyBatisConfig.java

package com.example.project5.config;import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class MyBatisConfig {@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();ssfb.setTypeAliasesPackage("com.example.project5.domain");ssfb.setDataSource(dataSource);return ssfb;}@Beanpublic MapperScannerConfigurer mapperScannerConfigurer(){MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();mapperScannerConfigurer.setBasePackage("com.example.project5.dao");return mapperScannerConfigurer;}}

dao/AccountDao.java

package com.example.project5.dao;import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;@Repository
public interface AccountDao {@Update("update account set money = money + #{money} where username = #{name}")void addMoney(@Param("name") String username, @Param("money") Double money);@Update("update account set money = money - #{money} where username = #{name}")void outMoney(@Param("name") String username, @Param("money") Double money);
}

service/AccountService.java

package com.example.project5.service;public interface AccountService {/*** 转账操作* @param out 转出方* @param in 转入方* @param money 金额*/public void transfer(String out, String in, double money);
}

service/impl/AccountServiceImpl.java

package com.example.project5.service.impl;import com.example.project5.dao.AccountDao;
import com.example.project5.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;@Overridepublic void transfer(String out, String in, double money) {accountDao.outMoney(out, money);accountDao.addMoney(in, money);}
}

测试方法

package com.example.project5;import com.example.project5.config.SpringConfig;
import com.example.project5.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Project5ApplicationTests {@Autowiredprivate AccountService accountService;@Testpublic void testTransfer() {accountService.transfer("aaa", "bbb", 20);}}

执行测试代码后,测试代码不会产生任何输出,但数据库中aaa的金额会由100变成80,bbb的金额会由111变成131:
在这里插入图片描述

问题分析

假如在AccountServiceImpl中手动制造一个错误:

package com.example.project5.service.impl;import com.example.project5.dao.AccountDao;
import com.example.project5.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;@Overridepublic void transfer(String out, String in, double money) {accountDao.outMoney(out, money);int a = 1/0;accountDao.addMoney(in, money);}
}

这时候,程序在执行完outMoney方法,也就是aaa转出了20之后就不会继续执行了,这20并没有转入到bbb的账户之中,这就是事务的不一致性。接着上面的aaa金额为80,bbb的金额为131执行这个会报错的代码,结果是:
在这里插入图片描述

对运行的结果简单进行分析:
在这里插入图片描述
我们需要进行事务管理,使得数据层中的数据同加同减,而不是分开操作

事务管理三步

第一步:在业务层接口上加上注解@Transactional

package com.example.project5.service;import org.springframework.transaction.annotation.Transactional;public interface AccountService {/*** 转账操作* @param out 转出方* @param in 转入方* @param money 金额*/@Transactionalpublic void transfer(String out, String in, double money);
}

在这里插入图片描述

第二步:在JdbcConfig.java中注册事务管理器

package com.example.project5.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;
import java.lang.management.PlatformLoggingMXBean;public class JdbcConfig {@Value("${jdbc.driver}")String driver;@Value("${jdbc.url}")String url;@Value("${jdbc.username}")String username;@Value("${jdbc.password}")String password;@Beanpublic DataSource dataSource(){DruidDataSource ds = new DruidDataSource();ds.setDriverClassName(driver);ds.setUsername(username);ds.setPassword(password);ds.setUrl(url);return ds;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}}

在这里插入图片描述

第三步:在SpringConfig.java上加上开启事务管理的注解@EnableTransactionManagement

package com.example.project5.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration
@PropertySource("classpath:jdbc.properties")
@ComponentScan("com.example.project5")
@Import({JdbcConfig.class, MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}

启用事务管理后,保持刚才会报错的AccountServiceImpl.java的代码,恢复aaa金额为80,bbb金额为131,并再次进行测试,此时数据库中的内容不会发生任何改变:
在这里插入图片描述

3. Spring事务角色

在事务没有开启的时候:
在这里插入图片描述
outMoneyinMoney分别对应一个事务,我们手动写的异常是写在事务T1和事务T2之间的,则事务T1执行完毕以后发生了异常,所以事务T2不再执行
为了将两个事务统一起来,统一执行,或者统一不执行,我们在transfer方法上加了注解@Transactional,此时transfer本身是一个事务,我们将outMoneyinMoney都加入到这个事务中来:
在这里插入图片描述
此时我们将transfer方法称为事务管理员outMoneyinMoney称为事务协调员,具体定义如下:
在这里插入图片描述

4. Spring事务属性

事务配置

在@Transactional中还有很多属性
在这里插入图片描述
这里需要说明的是rollbackFor,默认的事务回滚,在我们没有定义rollbackFor的时候,只会在程序中出现运行时异常时候进行回滚,比如我们刚才手动指定的1/0就属于一个运行时抛出异常,假如修改这个异常如下:

package com.example.project5.service.impl;import com.example.project5.dao.AccountDao;
import com.example.project5.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.IOException;@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;@Overridepublic void transfer(String out, String in, double money) throws IOException {accountDao.outMoney(out, money);if(true) throw new IOException();accountDao.addMoney(in, money);}
}

再执行测试代码,就会发现数据库中的内容会从(aaa:80,bbb:131)->(aaa:60,bbb:131)
再次印证:没有定义rollbackFor的时候,只会在程序中出现运行时异常时候进行回滚
那么我们定义一下rollbackFor属性,如下:

package com.example.project5.service;import org.springframework.transaction.annotation.Transactional;import java.io.IOException;public interface AccountService {/*** 转账操作* @param out 转出方* @param in 转入方* @param money 金额*/@Transactional(rollbackFor = {IOException.class})public void transfer(String out, String in, double money) throws IOException;
}

再执行测试代码,就会发现数据库中的内容(aaa:60,bbb:131)->(aaa:60,bbb:131),没有发生改变,所以我们需要通过rollbackFor来指定一些非运行时异常,在定义rollbackFor以后,程序在遇到运行时异常仍会回滚。

案例:转账业务追加日志

在这里插入图片描述

案例代码

在上述案例代码中加上如下内容:

代码结构

在这里插入图片描述

数据库表

在这里插入图片描述

dao/LogDao.java
package com.example.project5.dao;import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;import java.util.Date;@Repository
public interface LogDao {@Insert("insert into log(content, date) VALUES(#{content}, #{date})")void insertLog(@Param("content") String content, @Param("date") Date date);
}
service/LogService.java

注意,该方法上也要加上事务注解

package com.example.project5.service;import java.util.Date;public interface LogService {@Transactionalvoid insertLog(String content, Date date);
}
service/LogServiceImpl.java
package com.example.project5.service.impl;import com.example.project5.dao.LogDao;
import com.example.project5.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.Date;@Service
public class LogServiceImpl implements LogService {@AutowiredLogDao logDao;@Overridepublic void insertLog(String content, Date date) {logDao.insertLog(content, date);}
}
修改service/impl/AccountServiceImpl.java如下
package com.example.project5.service.impl;import com.example.project5.dao.AccountDao;
import com.example.project5.service.AccountService;
import com.example.project5.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.util.Date;@Service
public class AccountServiceImpl implements AccountService {@AutowiredAccountDao accountDao;@AutowiredLogService logService;@Overridepublic void transfer(String out, String in, double money) throws IOException {try{accountDao.outMoney(out, money);accountDao.addMoney(in, money);            } finally {logService.insertLog(out + "向" + in + "转账" + money + "元", new Date());}}
}

当捕捉到异常时执行日志记录。
将数据库中的金额恢复为:aaa->100,bbb->111,并执行测试代码,得到account表和log表的结果:
在这里插入图片描述
在这里插入图片描述
正常执行的时候,会修改数据库中的金额、向日志记录中添加日志
假设我们在AccountServiceImpl的try中加上:

accountDao.outMoney(out, money);
int a = 1/0;
accountDao.addMoney(in, money);

我们期望的结果是:不修改数据库中的金额、向日志记录中添加日志,使用修改后的代码再执行测试方法,得到结果是account表和log表中的内容都没有发生任何变化,所以我们归纳总结出存在的问题:

在这里插入图片描述

改进

我们需要定义事务的传播属性propagation,在LogService.java下重新写注解,改为:

@Transactional(propagation = Propagation.REQUIRES_NEW)

此时,再次运行上面的代码,结果为:
account表中的内容不变,log表中新添了日志:
在这里插入图片描述
在这里插入图片描述
我认为这样的改进可以理解为,使用默认的propagation时,事务协调员都被添加到事务管理员的事务中,从而统一提交或统一回滚:
在这里插入图片描述
当我们在LogService上写明了事务的传播行为为Requires_New后,即使原有了事务,我们还是会为这个service实例开启一个新事务,如下,这样就不是统一受到事务t的控制了:
在这里插入图片描述

事务传播行为

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Mode Management】ComM详细介绍

目录 1. Introduction and functional overview 2.Dependencies to other modules 3.Functional specification 3.1 Partial Network Cluster Management 3.2 ComM channel state machine 3.2.1 Behaviour in state COMM_NO_COMMUNICATION 3.2.1.1 COMM_NO_COM_NO_PENDI…

暂退法(丢弃法)

在深度学习中&#xff0c;丢弃法&#xff08;Dropout&#xff09;是一种常用的正则化技术&#xff0c;旨在减少模型的过拟合现象&#xff0c;可能会比之前的权重衰减(Weight Decay)效果更好。通过在训练过程中随机丢弃一部分神经元&#xff0c;可以有效地减少神经网络中的参数依…

Python实验项目9 :网络爬虫与自动化

实验 1&#xff1a;爬取网页中的数据。 要求&#xff1a;使用 urllib 库和 requests 库分别爬取 http://www.sohu.com 首页的前 360 个字节的数据。 # 要求&#xff1a;使用 urllib 库和 requests 库分别爬取 http://www.sohu.com 首页的前 360 个字节的数据。 import urllib.r…

微服务最佳实践:构建可扩展且高效的系统

微服务架构彻底改变了现代软件开发&#xff0c;提供了无与伦比的敏捷性、可扩展性和可维护性。然而&#xff0c;有效实施微服务需要深入了解最佳实践&#xff0c;以充分发挥微服务的潜力&#xff0c;同时避免常见的陷阱。在这份综合指南中&#xff0c;我们将深入研究微服务的关…

跟iPhone类似,不同品牌的手机、电脑随时使用“隔空投送”功能!如何开启?

iPhone的隔空投送是一个很受欢迎的功能。打开一个 App&#xff0c;然后轻点“共享”或“共享”按钮&#xff0c;再点击隔空投送&#xff0c;就可以分享图片、视频、文件出去。 然而&#xff0c;如果你用的不是苹果的产品&#xff0c;iPhone的隔空投送功能就有了“隔阂”。 不过…

CountDownLatch实战应用——实现异步多线程业务处理,异常情况回滚全部子线程

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; CountDownLatch实战应用——实现异步多线程业务处理&#xff0c;异常情…

H266/VVC编码标准介绍

视频编码标准 多样的视频应用催生了多种的视频编码方法。为了使编码后的码流能够在大范围内通用和规范&#xff0c;从20世纪80年代开始&#xff0c;国际组织就开始对视频编码建立国际标准。 什么是视频编码标准&#xff1a; 视频编码标准只规定了码流的语法语义和解码器&#…

Appium —— 初识移动APP自动化测试框架Appium

说到移动APP自动化测试&#xff0c;代表性的测试框架非Appium莫属&#xff0c;从今天开始我们将从APP结构解析、Appium框架学习、安卓/iOS自动化测试实战、自动遍历回归测试、自动化测试平台及持续集成&#xff0c;多个维度一起由浅入深的学废Appium 今天我们先来初步认识Appi…

【消息中间件】Rabbitmq的基本要素、生产和消费、发布和订阅

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 文章目录 前言一、消息队列的基本要素1.队列:queue2.交换机:exchange3.事件:routing_key4.任务:task 二、生产消费模式1.安装pika2.模拟生产者进程3.模…

【HR培训】行为反馈复盘,走出舒适区--20231217

行为反馈复盘&#xff0c;走出舒适区–鱼缸会议 要点&#xff1a;在于建立平等、透明、敢说的反馈环境&#xff0c;不打断、不争论 鱼缸会议流程 导入——入缸——反馈——承诺——关闭 步骤1&#xff1a;导入 目的&#xff1a;平等、透明、敢说的反馈 人员&#xff1a;主…

maui中实现加载更多 RefreshView跟ListView(1)

效果如图&#xff1a; MainPage.xaml.cs: using System; using System.Collections.ObjectModel; using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Xaml; using System.ComponentModel; using System.Runtime.CompilerServices…

计算机网络基础——网线认识与制作,线缆类型、线序、端接标准及注意事项

一、引言 网线制作是网络基础知识中不可或缺的。网络传输过程中&#xff0c;网线的质量和制作方法都会直接影响传输的速度和稳定性。本文将详细介绍网线制作的基础知识、线缆类型、线序、端接标准及注意事项。希望通过本文&#xff0c;读者能够更好地了解和掌握网线制作的方法…

AMD 自适应和嵌入式产品技术日

概要 时间&#xff1a;2023年11月28日 地点&#xff1a;北京朝阳新云南皇冠假日酒店 主题内容&#xff1a;AMD自适应和嵌入式产品的更新&#xff0c;跨越 云、边、端的AI解决方案&#xff0c;赋能智能制造的机器视觉与机器人等热门话题。 注&#xff1a;本文重点关注FPGA&a…

ASP.NET MVC实战之权限拦截Authorize使用

1&#xff0c;具体的实现方法代码如下 public class CustomAuthorizeAttribute : FilterAttribute, IAuthorizationFilter{/// <summary>/// 如果需要验证权限的时候&#xff0c;就执行进来/// </summary>/// <param name"filterContext"></par…

Ubuntu系统入门指南:基础操作和使用

Ubuntu系统的基础操作和使用 一、引言二、安装Ubuntu系统三、Ubuntu系统的基础操作3.1、界面介绍3.2、应用程序的安装和卸载3.3、文件管理3.4、系统设置 四、Ubuntu系统的日常使用4.1、使用软件中心4.2、浏览器的使用和网络连接设置4.3、邮件客户端的配置和使用4.4、文件备份和…

HTML5+CSS3小实例:3D发光切换按钮效果

目录 一、运行效果 图片效果 二、项目概述 三、开发环境 四、实现步骤及代码 1.创建空文件夹 2.完成页面内容 3.完成css样式 五、项目总结 六、源码获取 一、运行效果 图片效果 二、项目概述 这个项目是一个演示3D发光切换按钮效果的网页。按钮由一个开关和一个指…

Linux之进程(四)(进程地址空间)

目录 一、程序地址空间 二、进程地址空间 1、概念 2、写时拷贝 3、为什么要有进程地址空间 四、总结 一、程序地址空间 我们先来看看下面这张图。这张图是我们在学习语言时就见到过的内存区域划分图。 下面我们在Linux下看一看内存区域是不是也是这么划分的。 可见在Li…

圣诞树绘制合集-python绘制

使用Python绘制迷人的圣诞树 引言 随着圣诞节的临近&#xff0c;我们都希望以各种方式庆祝这个欢乐的节日。作为一名编程爱好者&#xff0c;你有没有想过用Python来创造节日的气氛呢&#xff1f;在这篇文章中&#xff0c;我将向你展示如何用Python绘制几种不同风格的圣诞树&a…

索尼(ILCE-7M3)MP4文件只能播放前两分钟修复案例

索尼的ILCE-7M3是一款经典设备&#xff0c;其HEVC编码效果是比较不错的&#xff0c;因此受到很多专业人士的青睐。之前我们说过很多索尼摄像机断电生成RSV文件修复的案例&#xff0c;今天来讲一个特殊的&#xff0c;文件已经正常封装但仅能播放前两分钟多一点的画面。 故障文件…

详细教程 - 从零开发 鸿蒙harmonyOS应用 第四节 (鸿蒙Stage模型 登录页面 ArkTS版 推荐使用)

在鸿蒙OS中&#xff0c;Ability是应用程序提供的抽象功能&#xff0c;可以理解为一种功能。在应用程序中&#xff0c;一个页面即一种能力&#xff0c;如登录页面&#xff0c;即具有登录功能的能力。以下是对鸿蒙新建项目的登录代码功能的详细解读和工作流程的描述&#xff1a; …