【回顾原生JDBC手动管理事务以及两种方式实现Spring编程式事务】

server/2024/10/20 11:57:28/

文章目录

    • 一.关于事务
      • 1.事务概念
      • 2.事务四个基本特性
      • 3. 事务的生命周期
      • 4.事务的隔离级别
      • 5.事务的应用场景
    • 二.回顾原生JDBC手动管理事务
    • 三.Spring编程式事务
      • 1.使用 TransactionTemplate 进行编程式事务管理
      • 2.使用 PlatformTransactionManager 进行编程式事务管理
    • 四.编程式事务的应用场景
    • 五.总结

一.关于事务

1.事务概念

事务(Transaction) 是数据库操作中的一个基本概念,指的是一组作为单个工作单元执行的操作。这些操作要么全部执行成功并提交(commit),要么全部失败并回滚(rollback),确保数据库的一致性、完整性和可靠性。

事务通常应用于多步骤的数据库操作中,以确保数据在各种操作中的一致性,特别是在出现系统故障、错误或并发操作时。事务可以跨越多个SQL语句,这些语句共同构成一个完整的任务。

2.事务四个基本特性

事务的四个基本特性(ACID),事务的核心特性可以用 ACID 四个字母来概括:

  1. 原子性(Atomicity):
    事务中的所有操作要么全部成功,要么全部失败回滚。如果在事务执行过程中出现任何错误,已经执行的操作必须撤销(回滚),数据库恢复到事务开始前的状态。
  2. 一致性(Consistency):
    事务开始前和结束后,数据库必须处于一致的状态。事务的执行不能破坏数据库的规则(如约束、触发器等),任何违反数据库一致性规则的操作都会导致事务失败并回滚。
  3. 隔离性(Isolation):
    事务的执行过程与其他事务是隔离的,一个事务的执行不应受到其他事务的干扰。并发事务互不影响,每个事务的中间状态对其他事务不可见。
    数据库系统提供不同的隔离级别(如读未提交、读已提交、可重复读、可串行化)来控制并发事务之间的相互影响。
  4. 持久性(Durability):
    一旦事务提交,它对数据库的修改是永久的,即使系统发生故障也不会丢失。数据库系统通常会使用日志、备份等机制确保事务的持久性。

3. 事务的生命周期

  1. 开始事务:
    通过客户端或应用程序显式发起一个事务。在这期间执行的所有操作都被认为是这个事务的一部分。
  2. 执行操作:
    事务中执行多个SQL语句,如插入、更新、删除等。
  3. 提交事务:
    如果所有操作都成功,事务提交,数据库将保存这些操作的结果,数据状态更新。
  4. 回滚事务:
    如果事务中某些操作失败或发生错误,事务回滚,数据库将撤销已经执行的操作,恢复到事务开始前的状态。

4.事务的隔离级别

为了控制多个事务并发执行时的互相影响,数据库提供了多种隔离级别:

‌‌读未提交(Read Uncommitted)‌:最低的隔离级别,允许一个事务读取另一个事务未提交的数据。这可能导致脏读、不可重复读和幻读问题。
‌‌读已提交(Read Committed)‌:允许一个事务只能读取已经提交的其他事务的数据。这解决了脏读的问题,但仍可能存在不可重复读和幻读问题。
‌可重复读(Repeatable Read)‌:保证一个事务在执行期间多次读取相同的数据时,其结果是一致的。解决了不可重复读的问题,但仍可能存在幻读问题。
‌‌串行化(Serializable)‌:提供最高的隔离级别,通过强制事务串行执行来解决所有并发问题,包括脏读、不可重复读和幻读。虽然能够确保数据的完全一致性,但也导致了性能上的损失。

‌MySQL默认的隔离级别是可重复读(Repeatable Read)。‌‌在这个级别下,事务在读取数据时,会锁定读取的数据,防止其他事务对这些数据进行修改,直到当前事务提交或回滚。这意味着在一个事务中,读取的数据保持一致性,并且在事务结束之前不会被其他事务修改。

5.事务的应用场景

  • 银行转账:A用户向B用户转账,涉及A账户扣款和B账户入账,这两步操作必须作为一个事务,确保要么两步都成功,要么都失败。
  • 订单处理:在电商系统中,生成订单和扣除库存往往需要保证原子性,避免出现生成了订单但库存未更新的情况。

举例说明事务:事务中的两步操作,从账户A中扣除100元。向账户B中添加100元。这两步操作必须放在一个事务中。如果第一步成功,第二步失败(比如网络错误),数据库应回滚第一步的操作,恢复到未扣除100元的状态。

BEGIN TRANSACTION;-- 第一步:从A账户扣除100元
UPDATE accounts SET balance = balance - 100 WHERE user_id = 'A';-- 第二步:向B账户添加100元
UPDATE accounts SET balance = balance + 100 WHERE user_id = 'B';-- 如果两步操作都成功,提交事务
COMMIT;-- 如果任何一步失败,回滚事务
ROLLBACK;

事务的核心作用是通过ACID特性,确保数据操作在各种可能的异常情况下仍保持一致性和可靠性,防止数据错误和损坏。通过事务的使用,开发者可以更加自信地处理复杂的数据库操作,确保数据在不同步骤间保持一致。

二.回顾原生JDBC手动管理事务

为什么需要完全记住并且背熟JDBC流程? 因为这样去看Spring声明式事务或者去了解分布式事务,能让我们抓住主线,关注到重要的东西。

java">public static void main(String[] args) {String url = "jdbc:mysql://localhost:3306/xiaofa_lawyer";String user = "root";String password = "123456";Connection conn = null;PreparedStatement pstmt1 = null;PreparedStatement pstmt2 = null;try {// 1. 建立数据库连接conn = DriverManager.getConnection(url, user, password);// 2. 关闭自动提交,开始手动管理事务conn.setAutoCommit(false);// 3. 执行SQL操作String sql1 = "INSERT INTO t_user1 (name) VALUES (?)";pstmt1 = conn.prepareStatement(sql1);pstmt1.setString(1, "jinbiao111");pstmt1.executeUpdate();String sql2 = "INSERT INTO t_user2 (name) VALUES (?)";pstmt2 = conn.prepareStatement(sql2);pstmt2.setString(1, "jinbiao222");pstmt2.executeUpdate();// 4. 如果SQL操作成功,提交事务conn.commit();System.out.println("事务提交成功。");} catch (SQLException e) {// 5. 如果发生异常,回滚事务if (conn != null) {try {System.out.println("发生错误,回滚事务。");conn.rollback();} catch (SQLException rollbackEx) {rollbackEx.printStackTrace();}}e.printStackTrace();} finally {// 6. 关闭资源try {if (pstmt1 != null) pstmt1.close();if (pstmt2 != null) pstmt2.close();if (conn != null) conn.close();} catch (SQLException e) {e.printStackTrace();}}}

测试结果,这里因为没有异常,对两张表都插入成功,日志提示:“事务提交成功。”

三.Spring编程式事务

在Spring中,除了通过声明式事务(使用注解或XML配置)管理事务外,还可以使用编程式事务管理。编程式事务允许开发者在代码中通过API显式控制事务的开始、提交和回滚操作。Spring 提供了 TransactionTemplate 和 PlatformTransactionManager 两种主要的方式来实现编程式事务管理。

1.使用 TransactionTemplate 进行编程式事务管理

TransactionTemplate 是Spring中推荐使用的方式,能够简化事务管理,它通过模板模式封装了事务处理逻辑。

java"> @Autowiredprivate PlatformTransactionManager platformTransactionManager;/*** 编程式事务1:使用 TransactionTemplate 进行编程式事务管理*/public void programmingTransaction1() {TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {try {// 执行业务逻辑jdbcTemplate.update("insert into t_user1 (name) values (?)", "jinbiao333");log.info("事务中执行的操作");// 你可以根据特定条件回滚事务if (someCondition()) {log.info("事务回滚");status.setRollbackOnly();  // 手动回滚事务}} catch (Exception e) {log.info("事务回滚");status.setRollbackOnly();  // 发生异常时自动回滚throw e;}}});}/***  判断满足某个条件事务回滚* @return*/private boolean someCondition() {return true;}

2.使用 PlatformTransactionManager 进行编程式事务管理

java">     /*** 编程式事务2:使用 PlatformTransactionManager 进行编程式事务管理*/public void programmingTransaction2() {//定义一个数据源DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");druidDataSource.setUrl("jdbc:mysql://localhost:3306/xiaofa_lawyer");druidDataSource.setUsername("root");druidDataSource.setPassword("123456");//定义一个JdbcTemplate,用来方便执行数据库增删改查JdbcTemplate jdbcTemplate = new JdbcTemplate(druidDataSource);//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(druidDataSource);//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();//3.获取事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);//4.执行业务操作,下面就执行2个插入操作try {log.info("before:{}", jdbcTemplate.queryForList("SELECT * from t_user1"));jdbcTemplate.update("insert into t_user1 (name) values (?)", "jinbiao555");jdbcTemplate.update("insert into t_user2 (name) values (?)", "jinbiao666");//5.提交事务:platformTransactionManager.commitplatformTransactionManager.commit(transactionStatus);} catch (Exception e) {//6.回滚事务:platformTransactionManager.rollbackplatformTransactionManager.rollback(transactionStatus);}log.info("after:{}", jdbcTemplate.queryForList("SELECT * from t_user1"));}

编程式事务过程简化了一下,如下

  1. 定义事务属性信息:TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
  2. 定义事务管理器:PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
  3. 获取事务:TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
  4. 执行sql操作:比如上面通过JdbcTemplate的各种方法执行各种sql操作
  5. 提交事务(platformTransactionManager.commit)或者回滚事务(platformTransactionManager.rollback).

**PlatformTransactionManager接口有多个实现类,用来应对不同的环境。**比如你操作db用的是hibernate或者mybatis,那么用到的事务管理器是不一样的,常见的事务管理器实现有下面几个:

  • JpaTransactionManager:如果你用jpa来操作db,那么需要用这个管理器来帮你控制事务。
  • DataSourceTransactionManager:如果你用是指定数据源的方式,比如操作数据库用的是:JdbcTemplate、mybatis、ibatis,那么需要用这个管理器来帮你控制事务。
  • HibernateTransactionManager:如果你用hibernate来操作db,那么需要用这个管理器来帮你控制事务。
  • JtaTransactionManager:如果你用的是java中的jta来操作db,这种通常是分布式事务,此时需要用这种管理器来控制事务。

我们的案例中使用的是JdbcTemplate来操作db,所以用的是DataSourceTransactionManager这个管理器。

事务传播行为和隔离级别
在编程式事务中,可以通过 TransactionDefinition 设置事务的传播行为和隔离级别:

  • 传播行为:
  • PROPAGATION_REQUIRED(默认):加入现有事务或创建新事务。
  • PROPAGATION_REQUIRES_NEW:创建一个新事务,暂停当前事务。
  • PROPAGATION_SUPPORTS:支持现有事务,但如果没有事务,则以非事务方式执行。

四.编程式事务的应用场景

  • 需要灵活控制事务边界,尤其是在一个方法中根据不同条件决定是否提交或回滚事务。
  • 需要更复杂的事务逻辑,比如多个数据源之间的事务处理。
    相比于声明式事务,编程式事务虽然更灵活,但也更复杂。一般情况下,推荐使用声明式事务,只有在需要精细控制事务时才使用编程式事务。

五.总结

了解原生JDBC手动管理事务、Spring编程式事务,对我们学习Spring声明式事务,分布式事务还是有很大帮助的~


http://www.ppmy.cn/server/131794.html

相关文章

【C++网络编程】(一)Linux平台下TCP客户/服务端程序

文章目录 Linux平台下TCP客户/服务端程序服务端客户端相关头文件介绍 Linux平台下TCP客户/服务端程序 图片来源:https://subingwen.cn/linux/socket/ 下面实现一个Linux平台下TCP客户/服务端程序:客户端向服务器发送:“你好,服务…

你用过最好用的AI工具有哪些?探寻用户心中的最爱与最佳

随着人工智能技术的飞速发展,AI 工具如雨后春笋般涌现,广泛应用于各个领域。在 10 月 8 日至 10 月 27 日这段时间里,我们深入探讨了人们在使用 AI 工具时的偏好和体验,旨在揭示那些最受用户喜爱以及被认为最好用的 AI 工具&#…

无人机之视觉技术篇

一、视觉传感器的类型 摄像头: 最常见的视觉传感器,能够捕捉可见光图像和视频。 通过单目、双目或多目摄像头的组合,无人机能够实现立体视觉,从而估算距离、深度,并进行物体识别和追踪。 红外传感器: …

如何应对动态图片大小变化?Python解决网页图片截图难题

背景介绍 随着互联网的发展,许多网站,尤其是电商平台,如京东(JD.com),为了提升用户体验,采用了许多动态内容加载技术。当我们使用爬虫获取商品图片时,往往会遇到一些棘手问题&#…

【秋招笔试】10.08华为荣耀秋招(已改编)-(第二套)题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 大厂实习经历 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 本次的三题全部上线…

nest+数据库连接在长时间无活动后重连

在使用 NestJS 开发应用程序时,如果遇到数据库连接在长时间无活动后自动断开的问题,可以采取几种策略来解决这个问题。以下是一些常见的方法: 1. 使用连接池 NestJS 通常与 TypeORM 或 Sequelize 等 ORM 工具一起使用,这些工具都…

安卓13屏蔽蓝牙匹配对话框 自动匹配 android13屏蔽蓝牙匹配对话框 自动匹配

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 设置 蓝牙连接的时候,会有匹配对话框提示。我们来实现自动配对。 2.问题分析 这里我们是通过点击操作来实现功能的,所以我们思路可以是自动点击功能的实现。 3.代码分…

【网络代理模块】反向代理(下)

1 反向代理部署遇到的问题 1.1 通道异常断开导致数据丢失 将外网程序布置在云服务器上(外网)测试,ssh协议进行测试会莫名其快通道断开,发现丢了一个报文。丢报文的原因是:我们内网模块从命令通道接收到外网模块发来的…