分布式事务系列第二篇,回顾 Jdbc 事务

2年前 (2022) 程序员胖胖胖虎阿
140 0 0
分布式事务系列第二篇,回顾 Jdbc 事务

●Spring 教程下载

●SpringMVC 教程下载

●MyBatis 教程下载

●Spring Boot 教程下载

●2TB 免费 Java 学习资源

春节之前,松哥和大家说要开一个分布式事务的系列,年前发了一篇,这两天闲在家里,又撸了一篇,这是我们这个系列的第二篇。

第一篇参见:

  • 分布式事务开局第一篇,从数据库事务隔离级别说起

本文依然是一个铺垫,本文我们先来看看 Jdbc 事务。

环境:MySQL5.7

注意,MySQL5.x 和 MySQL8 在驱动类的路径以及数据库连接地址上写法有一些差别。

1. Jdbc事务

1.1 事务提交

Jdbc 默认开启了事务,并且开启了事务的自动提交,开发者可以通过如下代码修改这种默认的策略:

con.setAutoCommit(false);

然后通过如下代码手动提交或者回滚一个事务:

con.commit();con.rollback();

1.2 事务隔离级别

Jdbc 中默认的事务隔离级别和数据库保持一致,即如果开发者没有在 Jdbc 中设置数据库事务的隔离级别,默认的事务隔离级别就数据库的事务隔离级别,当然开发者在 Jdbc 中也可以通过如下代码修改事务隔离级别:

con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);con.setTransactionIsolation(Connection.TRANSACTION_NONE);

其中最后一个 NONE 表示不支持事务,另外四个则和数据库四个隔离级别一一对应。

1.2.1 READ COMMITTED

事务隔离级别的验证方式很简单,读者可以参考上篇文章,因为验证过程细节繁琐,本文主要向读者展示 READ COMMITTED 和 REPEATABLE READ 两种隔离级别。READ COMMITTED 具体操作步骤如下(数据源依然使用上文的数据源):

1.创建普通的 Maven 项目,添加数据库依赖:

<dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>5.1.27</version></dependency>

2.创建 DBUtils 类,提供数据库连接,如下:

public class DBUtils {    public static Connection getConnection() throws SQLException {        try {            Class.forName("com.mysql.jdbc.Driver");        } catch (ClassNotFoundException e) {            e.printStackTrace();        }        return DriverManager.getConnection("jdbc:mysql:///test01","root","root");    }}

3.接下来创建两个类 Main 和 Main2,相当于两个不同的 SQL 操作 session:

Main:

public class Main {    public static void main(String[] args) throws SQLException {        Connection con = DBUtils.getConnection();        con.setAutoCommit(false);        String sql = "update user set account=888 where username='zhangsan'";        PreparedStatement ps = con.prepareStatement(sql);        ps.executeUpdate();        con.commit();        ps.close();        con.close();    }}

Main2:

public class Main2 {    public static void main(String[] args) throws SQLException {        Connection con = DBUtils.getConnection();        con.setAutoCommit(false);        con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);        String sql = "select * from user where username='zhangsan'";        PreparedStatement ps = null;        ResultSet rs = null;        for (int i = 0; i < 3; i++) {            ps = con.prepareStatement(sql);            rs = ps.executeQuery();            while (rs.next()) {                long id = rs.getLong(1);                String username = rs.getString("username");                String account = rs.getString("account");                System.out.println("id:" + id + ";username:" + username + ";account:" + account);            }        }        con.commit();        rs.close();        ps.close();        con.close();    }}

注意,Main2 中有一个 for 循环,将同一个查询操作执行了 3 次,这个循环主要用来验证不可重复读问题和脏读问题。另外在 Main2 中将数据库隔离级别设置为 READ_COMMITTED。

4.在 Main2 类中 for 循环中的第一行代码处打断点,然后以 debug 方式启动 Main2,如图:

分布式事务系列第二篇,回顾 Jdbc 事务
2-1

由上图可以看到,目前数据库中 zhangsan 账户下有 999 块钱。接下来 Main2 类保持此状态不动。

5.在 Main 类中,提交事务的方法上打断点,然后以 debug 的方式启动 Main 类中的 main 方法,如下:

分布式事务系列第二篇,回顾 Jdbc 事务
2-2

6.接下来回到 Main2 类中,执行 for 循环的第二次遍历,结果如下:

分布式事务系列第二篇,回顾 Jdbc 事务
2-3

可以看到,第二次查询结果与第一次是一样的,说明脏读问题已经不存在了。

7.回到 Main 类中,提交事务。

8.回到 Main2,执行第三次 for 循环,发现数据已经发生了变化,即这里发生了不可重复读问题。如下:

分布式事务系列第二篇,回顾 Jdbc 事务
2-4

1.2.2 REPEATABLE READ

REPEATABLE READ 这种隔离级别很好测试,将 Main2 中如下一行注释掉,使用 MySQL 默认的隔离级别就行了。测试步骤与上文一致,这里就不再赘述。

con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

其他隔离级别的测试也基本一致,这里不再赘述了。

1.3 共享锁和排他锁

1.3.1 共享锁

共享锁又称读锁,是读取操作创建的锁。加了读锁的数据,可以被其他事务继续加读锁并发读取,但任何事务都不能对数据进行修改(也不能给事务加排他锁),直到已释放所有共享锁。用法:

SELECT ... LOCK IN SHARE MODE;

在查询语句后面增加 LOCK IN SHARE MODE,MySQL 就会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。

1.3.2 排他锁

排他锁又称写锁、独占锁,如果一条数据被加上了排他锁,其他事务无法再给该数据加任何锁,而获取排他锁的事务既能读数据,又能修改数据。

用法:

SELECT ... FOR UPDATE;

在查询语句后面增加 FOR UPDATE,MySQL 就会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。加了排他锁的操作,有点类似 SERIALIZABLE 的隔离级别。

小结

OK,本文和大家回顾一下 Jdbc 事务,下篇文章我们再来看 Spring 中的事务机制。

分布式事务系列第二篇,回顾 Jdbc 事务

分布式事务系列第二篇,回顾 Jdbc 事务

喜欢就点个"在看"呗^_^

本文分享自微信公众号 - 江南一点雨(a_javaboy)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

版权声明:程序员胖胖胖虎阿 发表于 2022年9月21日 上午11:16。
转载请注明:分布式事务系列第二篇,回顾 Jdbc 事务 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...