《SpringBoot 3:入门与应用实战》第 12 章 JDBC 与事务 JDBC 事务管理 阅读笔记 33

《SpringBoot 3:入门与应用实战》第 12 章 JDBC 与事务 JDBC 事务管理 阅读笔记 33

12.3 JDBC 事务管理

Dao 层编程中一个绕不过的重点就是事务。试想如果一组数据库操作没有事务支撑,那么当其中某一条 SQL 语句出现异常时无法回滚之前的操作,业务数据也就没有正确性可言,由此可见事务的重要性。先回顾与事务相关的概念和原生 JDBC 中操作事务的方式,之后再学习 Spring Framework 提供的事务管理和控制机制。

12.3.1 事务回顾

对于事务的概念想必读者都很清楚,简单地说,事务就是一组逻辑操作的组合,它们的执行结果要么全部成功,要么全部失败。JDBC 中的事务有以下 4 个特性。

  • 原子性。一个事务就是一个不可再分解的单位,事务中的操作要么全部执行,要么全部不执行,原子性强调的是事务的整体。
  • 一致性。事务执行后,所有的数据都应该保持一致的状态,一致性强调的是数据的完整性。
  • 隔离性。多个数据库操作并发执行时,多个并发事务执行之间要相互隔离,隔离性强调的是并发的隔离。
  • 持久性。事务执行完成后,它对数据的影响是永久性的,持久性强调的是操作的结果。

针对数据库的并发操作,可能会出现一些事务并发问题。事务并发操作中会出现以下 3 种问题。

  • 脏读。一个事务读到了另一个事务没有提交的数据。
  • 不可重复读。一个事务读到了另一个事务已提交修改的数据。对同一行数据查询两次,结果不一致。
  • 幻读。一个事务读到了另一个事务已提交的新增数据。对同一张表查询两次,前后出现新增的行,导致结果不一致。

针对上述3个问题,可以引出事务的4种隔离级别。

  • read uncommitted(读未提交)---不解决任何问题。
  • read committed(读已提交)---解决脏读。
  • repeatable read(可重复读)---解决脏读、不可重复读。
  • serializable(可串行化)---解决脏读、不可重复读、幻读。

上述 4 种隔离级别自上而下逐级增高,但并发性能逐级降低。MySQL 中默认的事务隔离级别是 repeatable read 可重复读,Oracle、PostgreSQL 中默认的事务隔离级别是 read committed 读已提交。

12.3.2 原生 JDBC 事务

1.JDBC 事务的基本使用

代码提供了一个非常简单且容易理解的原生 JDBC 事务使用,这是一个很简单的两次数据库请求,中间夹带了一个异常的构造,由于出现除零的算术异常,代码的运行结果是抛出异常,事务回滚。对于原生 JDBC 的事务操作,在开启事务时需要调用 Connection 的 setAutoCommit 方法来关闭自动提交(开启事务)​,在数据库操作执行完毕后需要手动调用 Connection 的 commit 方法提交事务,如果这期间操作出现异常,也要手动调用 Connection 的 rollback 方法回滚事务。

java 复制代码
package com.yangjunbo.springboot.jdbc.examplec;

import com.zaxxer.hikari.HikariDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JdbcTransactionApplication {
    
    public static void main(String[] args) throws SQLException {

        // 构造数据源
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://192.168.19.201:3306/spring-dao?characterEncoding=utf8");
        dataSource.setUsername("spring-dao");
        dataSource.setPassword("yyy123456.");

        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            // 开启事务
            connection.setAutoCommit(false);
            // 执行第一条SQL语句
            PreparedStatement statement = connection
                    .prepareStatement("insert into tbl_user (name, tel) values ('test', '12345')");
            statement.executeUpdate();
            // 制造一个除零异常
            int i = 1 / 0;
            // 执行第二条SQL语句
            statement = connection.prepareStatement("delete from tbl_user where id = 1");
            statement.executeUpdate();
    
            // 提交事务
            connection.commit();
        } catch (Exception e) {
            // 回滚事务
            connection.rollback();
        } finally {
            // 关闭连接
            if (connection != null) {
                connection.close();
            }
        }

    }

}

2.事务保存点

上面的例子很好理解,不过下面要介绍一个读者可能没有接触过的概念:事务保存点。或许在日常开发中会遇到一些特殊的场景,这些场景中的业务逻辑可能需要分段执行,如果出现异常后不需要全部回滚,就可以利用 JDBC 事务中的保存点机制。当程序运行中出现异常时,可以控制事务只回滚到某个保存点处,这样,保存点之前的 SQL 语句仍然会执行。

下面举一个简单的例子,代码中共发送了 3 条 SQL 语句,并且第一条 SQL 语句执行完毕后记录了一个保存点,然后执行剩余两条 SQL 语句。由于执行第二条 SQL 语句之后发生了除零异常,因此需要回滚事务,而由于有保存点的存在,执行 rollback 方法时会将事务回滚到保存点处,之后执行 commit 方法即可提交事务保存点之前的 SQL 语句。

java 复制代码
package com.yangjunbo.springboot.jdbc.examplec;

import com.zaxxer.hikari.HikariDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;

public class TransactionSavepointApplication {
    
    public static void main(String[] args) throws SQLException {
        // 构造数据源
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://192.168.19.201:3306/spring-dao?characterEncoding=utf8");
        dataSource.setUsername("spring-dao");
        dataSource.setPassword("yyy123456.");

        Connection connection = null;
        Savepoint savepoint = null;
        try {
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            // 执行第一条SQL语句
            PreparedStatement statement = connection
                    .prepareStatement("insert into tbl_user (name, tel) values ('test', '12345')");
            statement.executeUpdate();
            // 记录一个保存点
            savepoint = connection.setSavepoint();
            // 执行接下来的SQL语句,并构造算术异常
            statement = connection.prepareStatement("insert into tbl_account (user_id, money) values (2, 123)");
            statement.executeUpdate();
            int i = 1 / 0;
            statement = connection.prepareStatement("delete from tbl_user where id = 1");
            statement.executeUpdate();
    
            connection.commit();
        } catch (Exception e) {
            if (savepoint != null) {
                connection.rollback(savepoint);
                connection.commit();
            } else {
                connection.rollback();
            }
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }
}
相关推荐
学长毕业设计1 小时前
基于SpringBoot的民间艺术传承管理系统(源码+文档+讲解视频)
java·spring boot·后端
小蒜学长1 小时前
基于Springboot+Vue的环保行动志愿者招募系统设计与实现(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端
用户233376852182 小时前
Ubuntu上fpm多架构打包实战
后端
Wendy不吃榴莲2 小时前
# AI短剧教程 - 《后西游记》开播后,AI影视为什么更考验“连续讲故事”?
人工智能·笔记·学习·ai·视频
Agudamu11612 小时前
B站学习视频怎么变笔记:用 Ai好记 + Obsidian 搭建个人知识库的完整教程
人工智能·笔记·学习·音视频
JavaGuide2 小时前
轻量开源版 IDEA 来了!
前端·后端
Json____2 小时前
springboot开发高校选课管理系统:从零构建前后端分离的全栈实践
java·spring boot·后端·毕业设计·毕设·wwwoop.com
站大爷IP2 小时前
Python的列表推导式把我写崩了,原来圆括号和方括号不是一回事
后端
xcl09253 小时前
民宿预约系统开发实战:从需求分析到上线部署全流程解析
java·spring boot