ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java连接MySQL与数据交互:从JDBC到连接池的完整实战指南

Java连接MySQL与数据交互:从JDBC到连接池的完整实战指南

用 Java 连接 MySQL 数据库,看起来是个老掉牙的话题,但真正自己动手写过一遍的人都知道,这里面的坑比想象中多得多。尤其是刚接触 Java 后端的朋友,第一次调通 JDBC 代码,心情往往不是“终于学会了”,而是“总算跑通了”。我记得自己第一份项目里写 Java 连接 MySQL 时,被 ClassNotFoundException、CommunicationsException、Access denied 轮番轰炸,光一个数据库连接就折腾了一个下午,后来才发现不过是时区参数没写。

这篇内容就是想把 Java 连接 MySQL 并实现数据交互这件事彻底讲明白。核心关键词就两个:Java 连接 MySQL、数据交互。我会从环境准备讲到驱动依赖,再到完整的增删改查代码、事务与批量处理,最后把生产环境必用的连接池和调试经验也一并打包。适合刚学 Java Web 的在校生、第一次在公司项目里写数据库操作的初级开发,也适合那些框架用惯了、想回头补一补 JDBC 底层细节的朋友。把这套东西吃透,后面你再去看 MyBatis、JPA,会发现那些框架不过是一层漂亮的壳。

1. 为什么还要手写 JDBC:数据访问大厦的第一块砖

很多初学者会问,现在写 Java 项目,十个里有九个用 Spring Boot + MyBatis、JPA,直接上手 JDBC 还有必要吗?这个问题的答案,要看你对自己代码的理解深度。

1.1 JDBC 在 Java 数据访问体系里的定位

JDBC(Java Database Connectivity)是 Java 官方定义的一套数据库访问接口规范。它定义了一套接口,比如 Connection、Statement、ResultSet,数据库厂商负责实现这些接口,提供对应的驱动包。Java 代码通过这套统一接口操作不同的数据库,不需要关心 MySQL 和 Oracle 之间的底层差异。

打个比方,JDBC 就像电源插座的标准规格,MySQL 驱动、Oracle 驱动都是按照这个规格生产的插头。你的 Java 代码只对着插座写,不用管背后接的是哪家发电厂的线。

明白了这层关系,你就知道 MyBatis、Hibernate 这些框架,本质上是帮你把 JDBC 的样板代码封装了起来。你在 MyBatis 里写一条 SQL,框架底层还是要创建 Connection、准备 PreparedStatement、执行 executeQuery,只不过这些动作被隐藏了。所以框架入门容易,遇到问题排查难。一旦 SQL 报错、连接池满、事务回滚不生效,不懂 JDBC 的人往往不知从何下手,懂的人扫一眼日志就能定位到是哪个环节出了问题。

1.2 什么场景适合直接拿起 JDBC

现在都推荐用框架,动不动就上 Spring Boot,但有些场景其实直接撸 JDBC 更香。

一是小工具和命令行程序。我之前写过一些内部数据整理的极简工具,就数据库连接加查询逻辑,总共几百行。这种项目引入一整套 ORM 框架,光是配置类和依赖就比你业务代码还长,纯粹给自己找麻烦。

二是性能敏感的数据批处理。ORM 框架在批量插入、大批量查询时的自动映射、缓存管理都会带来额外开销。JDBC 的 PreparedStatement 配合 addBatch、executeBatch,可以发挥数据库驱动的极限性能,这一点框架很难超越。

三是学习框架原理。你只有亲手写过 JDBC 代码,理解了连接从哪来、SQL 怎么执行、结果集怎么映射,才能理解 MyBatis 具体做了什么、JPA 又为何这么“重”。我见过不少开发,迁移个 Spring Boot 版本,MyBatis 报一大堆错,焦虑得不行,其实就是底层 JDBC 驱动的行为变了,不懂底层的就完全抓瞎。

所以手写 JDBC 不是过时,而是基本功。这篇文章教的这套东西,是后续所有数据访问技术的基石。

2. 准备环境与驱动依赖:光有代码可不够

真到了动手阶段,第一步不是写代码,而是核对你的开发环境。环境不匹配,后面写的代码全是空中楼阁。

2.1 JDK 与 MySQL 版本怎么配

先说 JDK,现在写新代码最低也要用 JDK 8,能上 JDK 11 或者 17 更好。JDK 8 虽然还能用,但 Java 11 之后的 DriverManager 加载机制有些细微变化,加上 Spring Boot 3 已经强制要求 JDK 17,建议新项目直接上 JDK 17,省得以后再迁。

MySQL 这边要分两种情况。本地学习、自己调试,用 MySQL 5.7 或者 8.0 都可以,8.0 的默认认证方式是 caching_sha2_password,对驱动版本有要求。公司项目如果是老系统,可能还在 5.6、5.7,那驱动也要对应调整。我建议你本地直接装 MySQL 8.0 系列,毕竟新项目基本都往这个版本走。

版本核对具体分三项。第一,MySQL 服务要正常启动,命令行里能用 mysql -u root -p 登录进去。第二,要提前建好数据库和测试表,这些准备动作不要太随意,因为后边代码要用到。第三,记住端口号,MySQL 默认 3306,如果你用的是 XAMPP、宝塔这类集成环境,端口可能被改过,到时候连不上别怀疑代码,先怀疑端口。

2.2 驱动依赖怎么选:这里有个大坑

Java 连接 MySQL 需要驱动包,最常见的叫 mysql-connector-j。这个名字有个历史坑:老一点的版本叫 mysql-connector-java,从 8.0.31 开始官方把 artifact 的 groupId 改成了 com.mysql,artifactId 改成了 mysql-connector-j。如果你在网上搜到的是老坐标,复制到 Maven 里 configure 时可能有点绕。

用 Maven 搭建工程,直接在 pom.xml 里加依赖。这是最标准的写法:

<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency>

如果你是 5.x 的老驱动,对应的依赖坐标是:

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

版本怎么选?我的经验是,用 MySQL 8.0 数据库就配 8.0.x 的驱动,用 MySQL 5.7 数据库,8.0.x 驱动也能兼容,但有时会碰到连接属性不识别的问题。反过来,5.1.x 驱动连 MySQL 8.0 数据库,会直接报 Unable to load authentication plugin 'caching_sha2_password'。所以最省心的组合是 MySQL 8.0 数据库 + 8.x 驱动,想折腾的可以试试其他组合,但别在版本匹配上浪费太多时间。

另外需要注意 class 名称的差异。老驱动是 com.mysql.jdbc.Driver,新驱动是 com.mysql.cj.jdbc.Driver。虽然 JDBC 4.0 以后驱动可以自动注册,不需要手动加载,但很多老项目的代码里仍然写了 Class.forName("com.mysql.jdbc.Driver"),用新驱动时这一行会报 ClassNotFoundException,解决方法是把类名换成新的。

3. 完整实操:从连接数据库到增删改查

环境备齐,依赖配好,下面写代码。我建议你跟着这个流程走一遍,比单纯读代码清楚得多。

3.1 第一步:建立数据库连接

我们先解决从 Java 到 MySQL 的“握手”问题。下面这段代码是最基础的连接方式:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class JdbcConnectionDemo { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/test_db" + "?useSSL=false" + "&serverTimezone=Asia/Shanghai" + "&characterEncoding=utf8"; String user = "root"; String password = "你自己的密码"; try (Connection conn = DriverManager.getConnection(url, user, password)) { System.out.println("连接成功,数据库是: " + conn.getCatalog()); } catch (SQLException e) { e.printStackTrace(); } } }

这里面的 URL 是连接字符串,必须严格按照固定格式来写。它由几个部分组成:jdbc:mysql:// 是协议头,localhost 是数据库主机地址,3306 是端口,test_db 是数据库名,后面跟着的是连接参数。

jdbc:mysql://主机地址:端口/数据库名?参数1=值1&参数2=值2

我第一次学的时候最容易漏掉的就是参数部分。三个参数里,serverTimezone 几乎是必写的,因为 MySQL 8.x 驱动默认要求指定时区,不写大概率报 The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,一串乱码直接把人看懵。useSSL=false 是为了避免本地连接时做 SSL 握手,开发环境加上这个参数能节省连接时间。characterEncoding=utf8 是为了保证中文不乱码,如果你没写它,插入中文数据时经常会出现问号或者乱码。

这段代码用了 JDK 7 引入的 try-with-resources 语法,Connection 是 AutoCloseable 的子类,try 块结束自动关闭连接。这是一个好习惯,避免忘记写 conn.close()。很多初学者一开始不爱用这个语法,喜欢手工 close,最后要么少了一个关闭,要么在关闭时又抛出空指针,反而不如这样干净。

3.2 第二步:使用 Statement 还是 PreparedStatement

连接建立了,接下来执行 SQL。这里有两个选择,一个 Statement,一个 PreparedStatement。我直接给结论:写代码一律用 PreparedStatement,除非你的 SQL 字符串里没有一处变量拼接。

Statement 的典型写法是把 SQL 字符串拼出来:

Statement stmt = conn.createStatement(); String sql = "SELECT * FROM user WHERE username = '" + username + "'"; ResultSet rs = stmt.executeQuery(sql);

这种写法在 username 传入一段恶意字符串时,会造成什么结果?比如传入' OR '1'='1,整条 SQL 变成查询全部用户,这就是 SQL 注入的经典攻击方式。我见过不少老系统就是这么被爬走数据的,所以不要心存侥幸。

PreparedStatement 的写法和它完全不同:

String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery();

参数在 SQL 里用问号占位,后续通过 setString、setInt 这些方法把值传进去。驱动会把 SQL 结构和参数值分开处理,参数值经过转义,恶意字符串只是被当成普通字符串,无法改变 SQL 结构。PreparedStatement 还有一个好处:同样的 SQL 模板可以预编译,重复执行时性能更好,尤其适合循环插入数据的场景。

3.3 第三步:查询数据并处理结果集

连接和 SQL 都就绪了,下面我们写一个完整的查询流程。假设有一张 user 表,包含 id、username、age 三个字段。代码可以这样写:

String sql = "SELECT id, username, age FROM user WHERE age > ?"; try (Connection conn = DriverManager.getConnection(url, user, password); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, 20); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { int id = rs.getInt("id"); String username = rs.getString("username"); int age = rs.getInt("age"); System.out.println("ID=" + id + ", 用户名=" + username + ", 年龄=" + age); } } } catch (SQLException e) { e.printStackTrace(); }

ResultSet 是查询结果集,可以理解为一个游标式的二维表。rs.next() 让游标移动到下一行,如果有数据就返回 true,没有数据就返回 false。所以 while (rs.next()) 是遍历结果集的标准循环。取列值的时候,可以按下标取 rs.getInt(1),也可以按列名取 rs.getInt("id"),我强烈建议用列名,因为 SQL 一旦调整,按列名还能保持代码可读性,按标号很容易取错列。

这里有个细节容易被忽略:ResultSet 一旦关闭,再想获取数据就来不及了。所以处理结果集的逻辑必须在 try 块内完成,或者先把数据复制到一个 List 里,连接关闭后还能继续使用。我通常这么处理:

List<User> userList = new ArrayList<>(); while (rs.next()) { User u = new User(); u.setId(rs.getInt("id")); u.setUsername(rs.getString("username")); u.setAge(rs.getInt("age")); userList.add(u); } // 出了 try 块,连接关闭,userList 依然可用

3.4 第四步:增删改操作与事务管理

增删改和查询不同,它们不返回结果集,而是返回一个影响行数。用 executeUpdate() 执行,返回值是 int,代表被修改的行数。举个例子:

// 插入 String insertSql = "INSERT INTO user(id, username, age) VALUES (?, ?, ?)"; try (PreparedStatement ps = conn.prepareStatement(insertSql)) { ps.setInt(1, 1); ps.setString(2, "张三"); ps.setInt(3, 25); int rows = ps.executeUpdate(); System.out.println("插入影响行数:" + rows); } // 更新 String updateSql = "UPDATE user SET age = ? WHERE username = ?"; try (PreparedStatement ps = conn.prepareStatement(updateSql)) { ps.setInt(1, 26); ps.setString(2, "张三"); ps.executeUpdate(); } // 删除 String deleteSql = "DELETE FROM user WHERE username = ?"; try (PreparedStatement ps = conn.prepareStatement(deleteSql)) { ps.setString(1, "张三"); ps.executeUpdate(); }

如果你只有一条 SQL,上面的写法足够了。但实际业务里,经常有两个操作必须同时成功或者同时失败。最典型的例子是转账:A 账户扣钱、B 账户加钱,两条 SQL 只要一条失败,另一条就必须回滚,否则账就不平了。

JDBC 的事务控制有三个核心操作。默认情况下,Connection 的 autocommit 是 true,也就是每条 SQL 执行后自动提交,我们要先关掉。然后执行多条 SQL,全部成功后 commit(),任何一个环节出问题就 rollback() 回滚,最后在 finally 里恢复自动提交状态。代码是这样:

Connection conn = null; try { conn = DriverManager.getConnection(url, user, password); conn.setAutoCommit(false); String sql1 = "UPDATE account SET balance = balance - 100 WHERE username = 'A'"; String sql2 = "UPDATE account SET balance = balance + 100 WHERE username = 'B'"; try (PreparedStatement ps1 = conn.prepareStatement(sql1)) { ps1.executeUpdate(); } try (PreparedStatement ps2 = conn.prepareStatement(sql2)) { ps2.executeUpdate(); } conn.commit(); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

这里注意,close() 之前一定要先 setAutoCommit(true),否则连接回到连接池后,下一个人拿到这个连接会因为 autocommit 还是 false 而导致行为异常。这种问题极难排查,日志里看不到任何报错,但到处是事务不生效、数据时有时无的诡异现象。

批量操作也是生产环境的高频需求。如果要在循环里插入一万条数据,每条都用 executeUpdate(),性能极差。正确姿势是使用 addBatch + executeBatch:

String sql = "INSERT INTO user(username, age) VALUES (?, ?)"; try (Connection conn = DriverManager.getConnection(url, user, password); PreparedStatement ps = conn.prepareStatement(sql)) { conn.setAutoCommit(false); for (int i = 0; i < 10000; i++) { ps.setString(1, "用户" + i); ps.setInt(2, 20 + i % 50); ps.addBatch(); if (i % 1000 == 0) { ps.executeBatch(); } } ps.executeBatch(); conn.commit(); }

每凑够一批就 executeBatch 一次,而不是等全部积累完,是在内存与速度之间取一个平衡。最终数量少时一次性提交没问题,量大的时候分批是必须的。

4. 从能跑到跑好:生产环境必须直面的性能与安全问题

把上面的代码跑通了,你只是完成了“能连上、能增删改查”这一步。实际项目上线后,还会面临连接管理、SQL 注入、连接耗尽这些新问题。

4.1 SQL 注入到底是怎么回事

上面提到用 PreparedStatement 代替 Statement,这里展开说说原理。SQL 注入的本质是用户输入被拼进了 SQL 语句里,变成了 SQL 的一部分。看一个例子:

String sql = "SELECT * FROM user WHERE username = '" + input + "'";

如果用户输入的是admin' --,拼接之后变成:

SELECT * FROM user WHERE username = 'admin' -- '

在 MySQL 里,-- 后面是注释,所以这条语句完全忽略了后面的单引号,直接以 username = 'admin' 作为条件执行。如果你运气好,数据库里恰好有 admin 用户,数据就泄露了。更狠的攻击是' OR 1=1 --,拼接后变成查询所有用户。

PreparedStatement 之所以安全,是因为它把 SQL 语句的结构和执行参数分开了。结构在 prepareStatement 时已经确定,参数被当作了纯数据传给驱动,驱动会做转义处理,单引号变成普通字符,无论如何都成不了 SQL 结构的一部分。这是官方推荐的第一道防线,不需要任何额外配置,只要你在写代码时养成用 PreparedStatement 的习惯就够了。

4.2 连接池:高并发下的生命线

新手写 JDBC 代码,每执行一次操作就新开一个连接,用完就关,在低并发本地测试没问题。但到了线上,一个请求开一次连接,连接建立和断开的开销非常大,数据库连接数很快就撑不住了。

数据库连接池可以简单理解为提前建好了一批连接放在池子里,用的时候从池里取出一个,用完关掉其实是归还到池子,而不是真的断开。这样数据库服务端不用频繁创建和销毁线程,应用端也能快速拿到可用连接。

Java 生态里最常见的连接池是 HikariCP,Spring Boot 2.x 之后默认使用它。HikariCP 为什么快?它的字节码被高度优化过,并发获取连接时锁的粒度更细,连接有效性检测也更聪明。如果自己管理连接池,可以这样配:

<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency>
HikariConfig config = new HikariConfig(); config.setJdbcUrl(url); config.setUsername(user); config.setPassword(password); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); HikariDataSource dataSource = new HikariDataSource(config);

配置参数里,maximumPoolSize 不是越大越好,通常和数据库实例的 CPU 核数相关。我见过有人直接配 200,结果数据库连接被打满,应用反而变慢。连接池的大小应该结合数据库性能和业务并发量来调,一开始 10 到 20 个是合理的起点,之后通过压测慢慢往上调。

拿到 DataSource 之后,原来的代码要改成从 dataSource.getConnection() 获取连接,这样连接就由连接池来管理。如果你继续 new DriverManager.getConnection(),等于完全绕过了连接池,配置白做了。

4.3 关于驱动自动加载的常见误解

Java 6 之后,JDBC 驱动支持通过 ServiceProvider 机制自动注册,也就是 DriverManager 会在启动时扫描 classpath 下的 META-INF/services/java.sql.Driver 文件,自动加载驱动类。所以新项目里写不写 Class.forName("com.mysql.cj.jdbc.Driver") 其实都可以。

但老一点的项目里,这行代码还是很普遍。为什么?因为旧驱动不是自动注册的,必须手动加载。而且有些容器环境下,DriverManager 的自动发现机制可能失效,写了 Class.forName 反而更稳妥。我的建议是:写的时候不写无所谓,但看到老代码里这行时不要惊讶,更不要删了之后报错又怪别人。

5. 常见问题排查与避坑实录:跑一次就会遇到的意外

代码写完了,运行报错才是真正的开始。下面这些问题是初学者最容易碰到的,我把它们整理成了一张速查表,方便复盘时对照。

报错信息常见原因解决方法
Communications link failureMySQL 服务未启动、端口不对、防火墙拦截先确认 MySQL 是否启动,telnet 3306 测试端口连通性
Access denied for user 'root'@'localhost'用户名或密码错误,或者用户没有远程访问权限核对密码,确认用户是否有对应库的权限
Unknown database 'test_db'数据库不存在先通过命令行 CREATE DATABASE 创建库
ClassNotFoundException: com.mysql.cj.jdbc.Driver驱动依赖没加载,或者类名写错检查 Maven 依赖是否生效,确认类名是 cj 开头
The server time zone value is unrecognizedURL 里没有指定 serverTimezone增加 &serverTimezone=Asia/Shanghai
SSL connection errorURL 里没有禁用 SSL,或本地 MySQL 不支持 SSL增加 &useSSL=false 仅限开发环境
中文数据乱码连接字符集未设置,或表本身字符集不是 utf8mb4URL 增加 characterEncoding=utf8,确认表字符集
No operations allowed after connection closed连接已关闭,但代码还在使用 Connection 或 ResultSet检查资源释放逻辑,把取数放在 try 内完成

第一类容易踩的坑是连接失败。Communications link failure 是新手最常看到的报错,原因很多,但排查思路是有迹可循的。先在命令行用 mysql -u root -p 连一下本机 MySQL,如果命令行都连不上,说明 MySQL 服务没起来,跟 Java 代码没有任何关系。如果命令行能连上,再用 telnet 127.0.0.1 3306 试一下端口,通说明网络和服务都正常,问题大概率出在代码的 URL、用户名或密码上。

第二类坑是驱动版本不匹配。我之前就遇到过,MySQL 8.0 数据库配了 5.1.x 的驱动,项目启动时一直报 Unable to load authentication plugin,其实就是因为新版本 MySQL 默认的加密插件变了,老驱动不认。解决办法有两个,要么把驱动升到 8.x,要么给数据库新建一个 mysql_native_password 的用户,但明显升驱动更简单。

第三类坑是连接池配置不当。我见到过把 maximumPoolSize 配得异常大的,结果应用启动时数据库 CPU 直接飙高,因为连接池预热创建了大量连接。也有配置了连接池但代码里还是用 DriverManager 拿连接的,白白空跑连接池。还有前面提到的 autocommit 没恢复,这是最阴间的坑:某条连接执行了事务没提交,归还给连接池后,下一个人拿这个连接继续做操作,数据一直不落库,排查半天才发现是连接状态被污染了。

排查这类问题,我的经验是关掉连接池,直接用 DriverManager 裸连,能跑通说明业务代码没问题,剩下的锅基本在连接池配置上。反过来也一样,如果裸连报错,先排除驱动、URL、权限这些基础项。两层分开排查,问题范围立刻缩小一半。

6. 封个简易工具类:JDBC 的第二次升华

写到这里,你已经有能力完成一次完整的 Java 连接 MySQL 数据交互了。但如果每个业务方法里都重复写获取连接、创建 PreparedStatement、遍历 ResultSet 的代码,你会很快感到繁琐。实际项目中,我们通常会封装一个 JDBC 工具类,把连接的获取和资源的释放统一管理。

我提供一个极简版的工具类思路,你可以根据自己的项目再调整:

public class JdbcUtil { private static final String URL = "jdbc:mysql://localhost:3306/test_db" + "?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { /* ignore */ } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { /* ignore */ } } if (conn != null) { try { conn.close(); } catch (SQLException e) { /* ignore */ } } } }

注意,工具类里我用 try 块包裹了 close 方法,但忽略了异常。这不是不在乎异常,而是关闭资源时的异常通常不会影响主要业务流程,而且如果在 finally 里再抛出异常,反而会掩盖前面的业务异常。这是一种权衡,但也是线上代码常见的容错方式。

有了工具类之后,业务代码可以写成:

Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); ps = conn.prepareStatement("SELECT * FROM user WHERE age > ?"); ps.setInt(1, 20); rs = ps.executeQuery(); while (rs.next()) { System.out.println(rs.getString("username")); } } finally { JdbcUtil.close(conn, ps, rs); }

这套代码已经比较接近公司里真正在用的风格了。如果再进一步,你可以在这个基础上做一个简单的 BaseDao、泛型查询封装,或者直接引入 Spring 的 JdbcTemplate。你会发现,JdbcTemplate 的核心思想就是这个工具类的增强版:连接统一管理、重复代码消除、异常转换。理解了 JDBC 这层,你再看 Spring 的数据访问体系,真的会有一层窗户纸被捅破的感觉。

我记得自己第一次完整封装出这个工具类的时候,心里特别踏实,因为我知道哪怕离开框架,我也能独立完成数据库操作。这种底层能力,是面试时聊到数据库性能调优、事务边界、连接管理时能够深入对话的底气。

这篇文章的内容到这里就完整了。如果你还在因为框架报错而一头雾水,不妨像我当初一样,找个周末,从 DriverManager 开始,不依赖任何框架,手写一个 Java 连接 MySQL 的小项目,把增删改查、事务、批量操作都过一遍,你会收获一种“尽在掌握”的感觉。过程中遇到任何问题,欢迎回来对照这篇文章,一步一步排查。毕竟,JDBC 踩过的每一个坑,都是数据库编程这条路上的垫脚石。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进