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

devtools/2024/10/22 4:59:07/

文章目录

    • 一.关于事务
      • 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/devtools/125549.html

相关文章

【重学 MySQL】六十五、auto_increment 的使用

【重学 MySQL】六十五、auto_increment 的使用 创建表时使用 AUTO_INCREMENT特点和要求插入数据查看当前 AUTO_INCREMENT 值设置初始 AUTO_INCREMENT 值重置 AUTO_INCREMENT 值注意事项示例:组合主键和 AUTO_INCREMENTMySQL8.0 新特性:自增变量的持久化背…

正点原子学习笔记之汇编LED驱动实验

1 汇编LED原理分析 为什么要写汇编     需要用汇编初始化一些SOC外设     使用汇编初始化DDR、I.MX6U不需要     设置sp指针,一般指向DDR,设置好C语言运行环境 1.1 LED硬件分析 可以看到LED灯一端接高电平,一端连接了GPIO_3上面…

栈溢出0x0C 前置技能:栈迁移

栈迁移原因: 在完成一般的栈溢出攻击时,有一个充分条件是「栈上有足够的地方让攻击者进行布局」。通常的函数栈剩余空间是足够放置一些恶意指令的,但也有少数极端情况,例如仅能容纳一个 ret与一个 ebp。此时,一般的栈…

蓝桥杯:求平均年龄

#include<stdio.h> int main() { int num 0; float age 0,sum0; printf("请输入总人数: "); scanf_s("%d" ,& num); for (int i1; i <num;i) { scanf_s("%f", &age); sum age…

【部署篇】Redis-04哨兵模式部署(源码方式安装)

一、准备主机 Redis的哨兵模式是生产环境中常用的部署模式之一&#xff0c;解决数据容灾和单点故障问题&#xff0c;实现主从自动切换&#xff1b;生产环境中建议让sentinel&#xff08;哨兵&#xff09;单独部署&#xff0c;如果资源有限可以和数据节点部署在同一主机。 主…

Java:数据结构-LinkedList和链表(2)

一 LinkedList LinkedList的方法的实现 1.头插法 public class MyLinkedList implements IList{static class ListNode{public int val;public ListNode next;public ListNode prev;public ListNode(int val){this.valval;}}public ListNode head;public ListNode last;Overr…

neovim ubuntu中WARNING No clipboard tool found

我在vnc远程的ubuntu中做个临时开发&#xff0c;发现neovim无法复制文字&#xff0c;于是我:checkhealth查看了一下&#xff0c;测试结果如下&#xff1a; WARNING No clipboard tool found. Clipboard registers (" and "*) will not work.ADVICE::help clipboard …

flask项目框架搭建

目录结构 blueprints python包&#xff0c;蓝图文件&#xff0c;相当于路由组的概念,方便模块化开发 例如auth.py文件 from flask import Blueprint, render_templatebp Blueprint("auth", __name__, url_prefix"/auth")bp.route("/login") d…