MyBatis3源码深度解析(七)JDBC单连接事务

文章目录

    • 前言
    • 2.7 JDBC单连接事务
      • 2.7.1 事务的开启与提交
      • 2.7.2 事务隔离级别
        • 2.7.2.1 并发访问问题
          • (1)脏读
          • (2)不可重复读
          • (3)幻读
        • 2.7.2.2 事务隔离级别
          • (1)TRANSACTION_NONE:不支持事务
          • (2)TRANSACTION_READ_UNCOMMITTED:读未提交
          • (3)TRANSACTION_READ_COMMITTED:读提交
          • (4)TRANSACTION_REPEATABLE_READ:可重复读
          • (5)TRANSACTION_SERIALIZABLE:串行化
      • 2.7.3 事务中的保存点
    • 2.8 小结

前言

DatabaseMetaData接口中有一个supportsTransactions()方法,用于判断当前数据源是否支持事务。

事务用于提供数据完整性、正确的应用程序语义和并发访问的数据一致性。所有遵循JDBC规范的驱动程序都需要提供事务支持。

本节研究JDBC中的单连接事务。

2.7 JDBC单连接事务

2.7.1 事务的开启与提交

在JDBC API中,没有对应的方法显式地开启事务,因此何时开启一个新的事务是由JDBC驱动程序或数据库隐式决定的

通常情况下,当SQL语句需要开启事务但目前还没有事务时,会自动地开启一个新的事务。

对于什么时候提交或回滚事务,Connection接口提供了setAutoCommit(boolean autoCommit)commit()rollback()等方法来进行控制。

  1. setAutoCommit(boolean autoCommit)方法用于设置事务是否自动提交,默认情况下事务自动提交是开启的,每个SQL语句执行完毕后会自动地提交事务。
  2. 如果使用setAutoCommit(boolean autoCommit)方法禁用了事务的自动提交,则需要显式地调用commit()方法提交事务,或者调用rollback()方法回滚事务。

禁用事务自动提交一般适用于需要将多个SQL语句作为一个事务提交或者事务由应用服务器管理的情况。

2.7.2 事务隔离级别

事务隔离级别用于表示事务中对数据的操作对其他事务的“可见性”,主要用于解决数据并发访问中的可能会出现的问题,且会直接影响到并发访问的效率。

Connection接口中提供了一个setTransactionIsolation(int level)方法,用于设置当前驱动程序的事务隔离级别。

2.7.2.1 并发访问问题
(1)脏读

脏读是指在一个事务中读取到另一个事务中未提交的数据。例如,A事务修改了一条数据,但是未提交修改,此时A事务对数据的修改对其他事务是可见的,因此B事务中能够读取A事务未提交的修改。一旦A事务回滚,B事务中读取的就是不正确的数据。

下面用一个简单例子来解释。

在数据库中插入一条数据:

在数据库中插入一条数据
编写A事务和B事务的测试代码:

@Test
public void testA() {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {connection = DbUtils.getConnection();// 关闭事务自动提交connection.setAutoCommit(false);// 设置事务隔离级别为:读未提交(下文解释)connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);// 读取一条记录String sql = "select * from user where id = 1";statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);resultSet = statement.executeQuery(sql);resultSet.next();System.out.println("A事务读取的用户信息:" + new User(resultSet).toString());// 修改这条记录resultSet.updateString("name", "孙悟空");resultSet.updateRow();System.out.println("A事务修改后的用户信息:" + new User(resultSet).toString());// 此处打一个断点 ...// 回滚事务connection.rollback();} catch (Exception e) {e.printStackTrace();} finally {DbUtils.close(resultSet, statement, connection);}
}@Test
public void testB() {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {connection = DbUtils.getConnection();// 关闭事务自动提交connection.setAutoCommit(false);// 设置事务隔离级别为:读未提交connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);// 读取一条记录String sql = "select * from user where id = 1";statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);resultSet = statement.executeQuery(sql);resultSet.next();System.out.println("B事务读取的用户信息:" + new User(resultSet).toString());// 此处打一个断点 ...// 提交事务connection.commit();} catch (Exception e) {e.printStackTrace();} finally {DbUtils.close(resultSet, statement, connection);}
}

下面开始模拟脏读产生的过程:

  1. 以Debug方式执行A事务中的查询记录、修改记录操作,停到断点处,控制台打印修改前和修改后的用户数据;

  1. 以Debug方式执行B事务中的查询记录操作,停到断点处,控制台打印查询出来的用户数据,确实是A事务修改后但未提交的用户数据;

  1. 继续执行A事务,回滚修改操作,但B事务已经拿到了A事务修改后的用户数据,如果B事务对修改后的数据进一步处理,就是不符合要求的,这就产生脏读。
(2)不可重复读

不可重复读是指在同一个事务中,对于同一份数据的多次读取可能返回不同的结果。例如,A事务读取了一行数据,但此时B事务中修改了该行数据,A事务中再次读取该行数据将得到不同的结果。

下面用一个简单例子来解释。

在数据库中只有一条数据:

编写A事务和B事务的测试代码:

@Test
public void testA() {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {connection = DbUtils.getConnection();// 关闭事务自动提交connection.setAutoCommit(false);// 设置事务隔离级别为:读未提交connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);// 读取一条记录String sql = "select * from user where id = 1";statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);resultSet = statement.executeQuery(sql);resultSet.next();System.out.println("A事务首次读取的用户信息:" + new User(resultSet).toString());// 此处打一个断点 ...// 再次读取记录resultSet = statement.executeQuery(sql);resultSet.next();System.out.println("A事务再次读取的用户信息:" + new User(resultSet).toString());// 提交事务connection.commit();} catch (Exception e) {e.printStackTrace();} finally {DbUtils.close(resultSet, statement, connection);}
}@Test
public void testB() {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {connection = DbUtils.getConnection();// 关闭事务自动提交connection.setAutoCommit(false);// 设置事务隔离级别为:读未提交connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);// 读取一条记录String sql = "select * from user where id = 1";statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);resultSet = statement.executeQuery(sql);resultSet.next();System.out.println("B事务读取的用户信息:" + new User(resultSet).toString());// 修改这条记录resultSet.updateString("name", "孙悟空");resultSet.updateRow();System.out.println("B事务修改后的用户信息:" + new User(resultSet).toString());// 提交事务connection.commit();} catch (Exception e) {e.printStackTrace();} finally {DbUtils.close(resultSet, statement, connection);}
}

下面开始模拟不可重复读产生的过程:

  1. 以Debug方式执行A事务中的首次查询记录操作,停到断点处,控制台打印首次查询的用户数据;

  1. 直接执行B事务中的查询记录、修改记录操作,控制台打印处理结果,此时数据库记录也已被修改;


  1. 继续执行A事务,再次以相同的SQL查询用户数据,发现查询的数据是修改后的,这就产生了不可重复读的问题。

(3)幻读

幻读发生在多个事务同时读取和修改数据时。例如,当A事务正在读取一系列数据时,B事务可能会插入一些新的数据,然后提交事务。当A事务再次查询相同的记录集时,它可能会发现一些原本不存在的记录,这会导致数据的不一致性,给用户造成幻觉。

幻读和不可重复读的区别在于,不可重复读侧重于已存在数据的更改,而幻读侧重于新增数据的插入。

下面用一个简单例子来解释。

在数据库中只有一条数据:

编写A事务和B事务的测试代码:

@Test
public void testA() {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {connection = DbUtils.getConnection();// 关闭事务自动提交connection.setAutoCommit(false);// 设置事务隔离级别为:读未提交connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);// 查询记录集String sql = "select * from user";statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);resultSet = statement.executeQuery(sql);System.out.println("A事务首次读取的用户信息有:");while (resultSet.next()) {System.out.println(new User(resultSet).toString());}System.out.println("-----------------------");// 此处打一个断点 ...// 再次查询记录集resultSet = statement.executeQuery(sql);System.out.println("A事务再次读取的用户信息有:");while (resultSet.next()) {System.out.println(new User(resultSet).toString());}// 提交事务connection.commit();} catch (Exception e) {e.printStackTrace();} finally {// 关闭资源DbUtils.close(resultSet, statement, connection);}
}@Test
public void testB() {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {connection = DbUtils.getConnection();// 关闭事务自动提交connection.setAutoCommit(false);// 设置事务隔离级别为:读未提交connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);// 插入一条记录String sql = "INSERT INTO USER (NAME, age, phone, birthday) VALUES('user1', 18, '18705464523', '2000-02-21 10:24:30');";statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);statement.executeUpdate(sql);// 提交事务connection.commit();} catch (Exception e) {e.printStackTrace();} finally {// 关闭资源DbUtils.close(resultSet, statement, connection);}
}

下面开始模拟幻读产生的过程:

  1. 以Debug方式执行A事务中的首次查询记录集操作,停到断点处,控制台打印首次查询的用户数据;

  1. 直接执行B事务中的插入记录操作,控制台打印处理结果,此时数据库记录也增加了一条;

  1. 继续执行A事务,再次以相同的SQL查询用户数据集,发现查询的数据还包括B事务新增的,这就产生了幻读的问题。

2.7.2.2 事务隔离级别

JDBC遵循SQL:2003规范,定义了5种事务隔离级别:

(1)TRANSACTION_NONE:不支持事务
(2)TRANSACTION_READ_UNCOMMITTED:读未提交

这种事务隔离级别允许某一事务读取另一事务未提交更改的数据,这意味着可能会出现脏读、不可重复读、幻读现象。

【2.7.2.1 并发访问问题】的三个案例均将事务隔离级别设置为“读未提交”,经过实际测试,确实会发生脏读、不可重复读、幻读现象。

(3)TRANSACTION_READ_COMMITTED:读提交

这种事务隔离级别表示在某一事务中进行任何数据的更改,在提交之前对其他事务都是不可见的,这样可以防止脏读,但不能解决不可重复读、幻读问题。

这是MySQL驱动程序默认的事务隔离级别。

下面继续使用【2.7.2.1 并发访问问题】中的三个案例进行测试。

首先手动将事务隔离级别设置为TRANSACTION_READ_COMMITTED读提交,其余代码保持不变。

// 设置事务隔离级别为:读提交
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
  • 测试脏读问题:已解决
  1. 以Debug方式执行A事务中的查询记录、修改记录操作,停到断点处,控制台打印修改前和修改后的用户数据;

  1. 以Debug方式执行B事务中的查询记录操作,停到断点处,控制台打印查询出来的用户数据,确实是原来的数据,而不是A事务修改后但未提交的用户数据

  1. 继续执行A事务,回滚修改操作,但B事务拿到的是A事务修改前的用户数据,符合要求,脏读问题已被解决。
  • 测试不可重复读问题:未解决

  • 测试幻读问题:未解决

(4)TRANSACTION_REPEATABLE_READ:可重复读

这种事务隔离级别表示在某一事务中对同一数据进行多次读取时,可以得到相同的结果,并且其他事务插入数据的操作对该事务不可见,这样可以防止脏读、不可重复读,但不能解决幻读问题;

下面继续使用【2.7.2.1 并发访问问题】中的三个案例进行测试。

首先手动将事务隔离级别设置为TRANSACTION_REPEATABLE_READ可重复读,其余代码保持不变。

// 设置事务隔离级别为:可重复读
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
  • 测试脏读问题:已解决

  • 测试不可重复读问题:已解决

  1. 以Debug方式执行A事务中的首次查询记录操作,停到断点处,控制台打印首次查询的用户数据;

  1. 直接执行B事务中的查询记录、修改记录操作,控制台打印处理结果,此时记录已被修改;

  1. 继续执行A事务,再次以相同的SQL查询用户数据,发现查询的数据是修改前的,这就解决了不可重复读的问题。

  • 测试幻读问题:未解决
(5)TRANSACTION_SERIALIZABLE:串行化

这种事务隔离级别是最高的事务隔离级别,保证数据的一致性和完整性,可以防止脏读、不可重复读,、幻读问题,但是并发性较差。

下面继续使用【2.7.2.1 并发访问问题】中的三个案例进行测试。

首先手动将事务隔离级别设置为TRANSACTION_SERIALIZABLE串行化,其余代码保持不变。

// 设置事务隔离级别为:串行化
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
  • 测试脏读问题:已解决

  • 测试不可重复读问题:已解决

  • 测试幻读问题:已解决

  1. 以Debug方式执行A事务中的首次查询记录集操作,停到断点处,控制台打印首次查询的用户数据;

  1. 直接执行B事务中的插入记录操作,发现报错,无法打开新的事务。

  1. 继续执行A事务,再次以相同的SQL查询用户数据集,发现查询的数据是一样的,这就解决了幻读的问题。

  1. A事务提交后,再次执行B事务,发现可以成功执行,说明串行化等级下一次只能打开一个事务。

2.7.3 事务中的保存点

保存点是指通过事务中标记的一个中间点来对事务进行更细粒度的控制,一旦设置保存点,事务就可以归滚到保存点,而不影响保存点之前的操作。

DatabaseMetaData接口提供了supportsSavepoints()方法用于判断JDBC驱动程序是否支持保存点。

Connection接口提供了``setSavepoint()```方法用于在当前事务中设置保存点。如果该方法在事务外中调用,则会在该方法调用处开启一个新的事务。

该方法的返回值是一个Savepoint对象,该对象可作为COnnection接口的rollback()方法的参数,用于回滚到对应的保存点。

示例代码如下:

// ......
// 关闭事务自动提交
connection.setAutoCommit(false);
// 读取一条记录
String sql = "select * from user where id = 1";
statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
resultSet = statement.executeQuery(sql);
resultSet.next();
System.out.println("第一次读取的用户信息:" + new User(resultSet).toString());
// 第一次修改这条记录
resultSet.updateString("name", "孙悟空-修改1");
resultSet.updateRow();
System.out.println("第一次修改后的用户信息:" + new User(resultSet).toString());
// 设置保存点
Savepoint savepoint = connection.setSavepoint();
// 第二次修改这条记录
resultSet.updateString("name", "孙悟空-修改2");
resultSet.updateRow();
System.out.println("第二次修改后的用户信息:" + new User(resultSet).toString());
// 回滚到保存点
connection.rollback(savepoint);
// 再次读取这条记录
// 读取一条记录
resultSet = statement.executeQuery(sql);
resultSet.next();
System.out.println("第二次读取的用户信息:" + new User(resultSet).toString());
// 提交事务
connection.commit();
// ......

控制台打印执行结果:

第一次读取的用户信息:User{id=1, name='黑风怪', age=18, phone='18705464523', birthday=2000-02-21}
第一次修改后的用户信息:User{id=1, name='孙悟空-修改1', age=18, phone='18705464523', birthday=2000-02-21}
第二次修改后的用户信息:User{id=1, name='孙悟空-修改2', age=18, phone='18705464523', birthday=2000-02-21}
第二次读取的用户信息:User{id=1, name='孙悟空-修改1', age=18, phone='18705464523', birthday=2000-02-21}

在示例代码中,依次进行读取记录→第一次修改→设置保存点→第二次修改→回滚到保存点→再次读取记录→提交事务,第二次读取的结果恰好就是第一次修改后的结果,说明确实回滚到了保存点的位置。

保存点创建后,可以被手动释放。Connection接口提供了releaseSavepoint()方法,接收一个Savepoint对象为参数,用于释放保存点。保存点被释放后,如果试图通过rollback()方法回滚到保存点,则会抛出SQLException异常。

事务中创建的保存点在事务提交或回滚之后会自动释放,事务回滚到某一保存点之后,该保存点之后的保存点将会自动释放。

2.8 小结

第2章到此就梳理完毕了,本章的主题是:JDBC规范。回顾一下本章的梳理的内容:

(二)JDBC API简介
(三)Connection
(四)Statement
(五)ResultSet
(六)DatabaseMetaData
(七)JDBC单连接事务

更多内容请查阅分类专栏:MyBatis3源码深度解析

第3章主要梳理:MyBatis常用工具类。主要内容包括:

  • 使用SQL类生成语句;
  • 使用ScriptRunner执行脚本;
  • 使用SqlRunner操作数据库;
  • MetaObject详解;
  • MetaClass详解;
  • ObjectFactory详解;
  • ProxyFactory详解。

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

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

相关文章

ChatGPT 串接到 Discord - 团队协作好助理

ChatGPT 串接到 Discord - 团队协作好助理 ChatGPT 是由 OpenAI 开发的一个强大的语言模型,本篇文章教你如何串接 Discord Bot ,协助团队在工作上更加高效并促进沟通与协作。使 ChatGPT 发挥出最大的功效,进一步提升工作效率和团队协作能力。…

Redis 内存的优化

目录 前言 Redis 的内存碎片问题 判断Redis 内存碎片 如何清理内存碎片? 前言 我想讲一下怎么提高Redis 内存的利用率,redis 的数据是保存在内存中。对内存的利用率低,意味着存的数据很少,并不意味着就没有内存了&#xff0c…

【解读】OWASP大语言模型应用程序十大风险

OWASP大型语言模型应用程序前十名项目旨在教育开发人员、设计师、架构师、经理和组织在部署和管理大型语言模型(LLM)时的潜在安全风险。该项目提供了LLM应用程序中常见的十大最关键漏洞的列表,强调了它们的潜在影响、易利用性和在现实应用程序…

利用华为CodeArts持续交付项目演示流程

软件开发生产线(CodeArts)是面向开发者提供的一站式云端平台,即开即用,随时随地在云端交付软件全生命周期,覆盖需求下发、代码提交、代码检查、代码编译、验证、部署、发布,打通软件交付的完整路径&#xf…

力扣---腐烂的橘子

题目&#xff1a; bfs思路&#xff1a; 感觉bfs还是很容易想到的&#xff0c;首先定义一个双端队列&#xff08;队列也是可以的~&#xff09;&#xff0c;如果值为2&#xff0c;则入队列&#xff0c;我这里将队列中的元素定义为pair<int,int>。第一个int记录在数组中的位…

day15_集合_ArrayList

今日内容 零、 复习昨日 一、集合框架体系 二、Collection 三、泛型 四、迭代 五、List(ArrayList、LinkedList) 零、 复习昨日 日期解析的方法签名(字符串–>日期) Date parse(String s) 日期格式化的方法签名(日期–>字符串) String format(Date date) 运行时异常有哪些…

19、电源管理入门之微内核中的电源管理

目录 1. QNX电源管理框架 2. QNX客户端API库 3. QNX代码分析 4. Fuchsia中的电源管理 5. Minix中的电源管理 6. Harmony OS中的电源管理 之前介绍的电源管理机制基本都是在Linux中实现的,可以看到很复杂,各种框架,明明一个操作非要转来转去,而且在内核里面实现,跟内…

【HarmonyOS】ArkTS-联合类型

目录 联合类型实例 联合类型 联合类型是一种灵活的数据类型&#xff0c;它修饰的变量可以存储不同类型的数据。 语法&#xff1a;let 变量: 类型1 | 类型2 | 类型3 值 基于联合类型&#xff0c;变量可存不同类型数据 实例 // 需求&#xff1a;定义一个变量&#xff0c;存放…

Spring web开发(入门)

1、我们在执行程序时&#xff0c;运行的需要是这个界面 2、简单的web接口&#xff08;127.0.0.1表示本机IP&#xff09; package com.example.demo;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestCont…

【OD】算法二

开源项目热度榜单 某个开源社区希望将最近热度比较高的开源项目出一个榜单&#xff0c;推荐给社区里面的开发者。对于每个开源项目&#xff0c;开发者可以进行关注(watch)、收藏(star)、fork、提issue、提交合并请求(MR)等。 数据库里面统计了每个开源项目关注、收藏、fork、…

垃圾回收:JavaScript内存管理的利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

ChatGPT 控制机器人的基本框架

过去的一年&#xff0c;OpenAI的chatGPT将自然语言的大型语言模型&#xff08;LLM&#xff09;推向了公众的视野&#xff0c;人工智能AI如一夜春风吹遍了巴黎&#xff0c;全世界都为AI而疯狂。 OpenAI ChatGPT是一个使用人类反馈进行微调的预训练生成文本模型。不像以前的模型主…

MYSQL | 数据库到底是怎么来的?

“以史为鉴&#xff0c;可以让我们更深刻地理解现在&#xff0c;预见未来。” 要想知道一件东西是怎么发生的, 我们不妨把时间拨回关系型数据库被提出前后来探索。在信息技术飞速发展的今天&#xff0c;回望数据库管理系统的演进之路&#xff0c;我们可以深刻理解到技术进步如…

Go语言数据结构(二)堆/优先队列

文章目录 1. container中定义的heap2. heap的使用示例3. 刷lc应用堆的示例 更多内容以及其他Go常用数据结构的实现在这里&#xff0c;感谢Star&#xff1a;https://github.com/acezsq/Data_Structure_Golang 1. container中定义的heap 在golang中的"container/heap"…

Linux网络套接字之预备知识

(&#xff61;&#xff65;∀&#xff65;)&#xff89;&#xff9e;嗨&#xff01;你好这里是ky233的主页&#xff1a;这里是ky233的主页&#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ 目录 一、预备知识 1.理解源IP地址和目的IP地址 …

【解读】OWASP 大语言模型(LLM)安全测评基准V1.0

大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;是指参数量巨大、能够处理海量数据的模型, 此类模型通常具有大规模的参数&#xff0c;使得它们能够处理更复杂的问题&#xff0c;并学习更广泛的知识。自2022 年以来&#xff0c;LLM技术在得到了广泛的应…

leetcode 热题 100_搜索二维矩阵

题解一&#xff1a; 二叉搜索树&#xff1a;从矩阵右上角观察&#xff0c;结构类似二叉搜索树&#xff0c;因此可以用类似的解法来做。具体做法是双指针从右上角开始&#xff0c;向左下角逐步搜索&#xff0c;如果当前值比目标值大&#xff0c;则向下移动&#xff0c;如果当前值…

了解 HTTPS 中间人攻击:保护你的网络安全

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

mybatis-plus整合spring boot极速入门

使用mybatis-plus整合spring boot&#xff0c;接下来我来操作一番。 一&#xff0c;创建spring boot工程 勾选下面的选项 紧接着&#xff0c;还有springboot和依赖我们需要选。 这样我们就创建好了我们的spring boot&#xff0c;项目。 简化目录结构&#xff1a; 我们发现&a…

Qt 实现诈金花的牌面值分析工具

诈金花是很多男人最爱的卡牌游戏 , 每当你拿到三张牌的时候, 生活重新充满了期待和鸟语花香. 那么我们如果判断手中的牌在所有可能出现的牌中占据的百分比位置呢. 这是最终效果: 这是更多的结果: 在此做些简单的说明: 炸弹(有些地方叫豹子) > 同花顺 > 同花 > 顺…