JDBC:数据库访问的原始接口
目录
一、JDBC 基础入门:数据库访问的原始接口
-
JDBC 是什么?它在 Java 中扮演什么角色?
-
JDBC 工作原理图解(驱动 -> 连接 -> 执行 -> 关闭)
-
常见 JDBC 驱动类型及差异
-
第一个 JDBC 示例程序:连接数据库 + 执行查询
二、JDBC 核心 API 详解
-
核心接口介绍:
DriverManager
/Connection
/Statement
/PreparedStatement
/ResultSet
-
PreparedStatement 与 Statement 的区别与安全性(防止 SQL 注入)
-
ResultSet 遍历与数据提取技巧
-
JDBC 操作流程六步详解
三、JDBC 与事务控制
-
JDBC 中的事务控制:自动提交与手动提交
-
commit()
、rollback()
的使用与实战 -
多事务嵌套控制及注意事项
-
如何处理事务中断异常?面试常问陷阱题解析
四、JDBC 实战:增删改查 CURD 模板
-
通用 CURD 模板封装
-
批量插入与批处理优化(
addBatch()
/executeBatch()
) -
实战演练:实现一个简易 DAO 层
-
JDBC 与用户登录/注册/分页查询等典型业务示例
五、JDBC 性能优化与连接池技术
-
JDBC 性能瓶颈分析
-
为什么不能频繁开关连接?
-
常见连接池技术对比:C3P0、DBCP、HikariCP、Druid
-
HikariCP 实践:配置、监控、调优
六、JDBC 与数据库安全
-
防止 SQL 注入的最佳实践
-
参数预编译的作用与局限
-
数据库权限控制(从代码到数据库用户)
七、JDBC 与框架集成
-
Spring JDBC 简介:比原生 JDBC 更高效
-
Spring JDBC 模板(JdbcTemplate)使用指南
-
与 MyBatis 的对比(JDBC 和 ORM 之间的选择)
-
企业项目中 JDBC 的典型应用场景
八、常见问题与面试题精选
-
JDBC 与连接池的面试高频题解析
-
JDBC 如何实现事务控制?嵌套事务如何处理?
-
如何解决 JDBC 中连接泄漏问题?
-
面试官提问:“如果让你封装一个通用 JDBC 工具类,你怎么设计?”
一、JDBC 基础入门:数据库访问的原始接口
在 Java 开发中,无论你是否使用框架如 MyBatis、JPA、Hibernate,JDBC 始终是底层的根基。理解它,不仅能帮助你打好基本功,还能在面试中展示你对原理的掌控力。
1.1 JDBC 是什么?它在 Java 中扮演什么角色?
JDBC(Java Database Connectivity)是 Java 官方提供的一套 用于操作数据库的 API 接口规范,主要用来:
-
连接数据库(MySQL、Oracle、PostgreSQL 等)
-
执行 SQL(查询、插入、更新、删除)
-
获取执行结果
-
控制事务(提交 / 回滚)
📌 通俗理解:JDBC 就是 Java 和数据库之间的“翻译器”。
面试官常问:
“你写过 JDBC 吗?它和 MyBatis 的本质区别是什么?”
✅ 回答建议:
-
JDBC 是低层原生接口,MyBatis 是对 JDBC 的封装;
-
JDBC 更灵活但代码多,MyBatis 简洁但封装深;
-
理解 JDBC 原理有助于定位 MyBatis 的性能问题。
1.2 JDBC 的工作原理(流程图)
JDBC 工作流程大致如下:
加载驱动 → 获取连接 → 创建 Statement → 执行 SQL → 处理结果 → 关闭资源
你可以类比理解为:
你(Java 程序) → 说话翻译(JDBC 驱动) → 数据库 → 给出回应 → 翻译回来 → 你处理结果
关键类与接口如下:
步骤 | 对应类或方法 | 说明 |
---|---|---|
加载驱动 | Class.forName() | 注册 JDBC 驱动 |
获取连接 | DriverManager.getConnection() | 连接数据库 |
执行语句 | Statement / PreparedStatement | 发起 SQL |
处理结果 | ResultSet | 查询结果 |
关闭资源 | close() 方法 | 防止连接泄漏 |
1.3 常见 JDBC 驱动类型及区别
JDBC 驱动分为 4 种类型,了解即可,面试可重点记 Type 4。
类型 | 简介 | 示例 | 是否推荐 |
---|---|---|---|
Type 1 | JDBC-ODBC 桥接 | 早期版本,几乎废弃 | ❌ 不推荐 |
Type 2 | Java 到本地 API | 依赖本地库(DLL、so) | ❌ 移植性差 |
Type 3 | 中间服务器转发 | 稀有,复杂 | ❌ |
Type 4 | 纯 Java 驱动 | 如:com.mysql.cj.jdbc.Driver | ✅ 最常用、性能好 |
1.4 第一个 JDBC 示例程序
import java.sql.*; public class JdbcDemo {public static void main(String[] args) throws Exception {// 1. 加载驱动(MySQL 8 以后可省略)Class.forName("com.mysql.cj.jdbc.Driver"); // 2. 获取连接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC", "root", "password"); // 3. 创建 Statement 执行 SQLString sql = "SELECT id, name FROM users";Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql); // 4. 处理结果while (rs.next()) {int id = rs.getInt("id");String name = rs.getString("name");System.out.println("用户ID: " + id + ",用户名:" + name);} // 5. 关闭资源rs.close();stmt.close();conn.close();} }
✅ 重点提醒:
-
要导入 MySQL JDBC 驱动 jar 包(如:
mysql-connector-j
) -
使用完连接必须关闭,避免“连接泄漏”
-
实际项目中不直接使用
Statement
,而是用PreparedStatement
总结
-
JDBC 是数据库访问的基础技能,虽然在项目中被框架封装,但面试时经常会问底层细节;
-
记住六步流程,尤其是连接获取、预编译 SQL、防止注入;
-
JDBC 是学习 Spring JDBC、MyBatis 的前提。
好的,下面是第二章的正文内容,继续坚持“原理 + 实战 + 面试”的思路,帮助 Java 学习者全面掌握 JDBC 核心接口。
二、JDBC 核心 API 详解
学习 JDBC,最绕不开的就是这几个核心接口:DriverManager
、Connection
、Statement
、PreparedStatement
、ResultSet
。这些东西不仅写代码用得上,面试时也常被用来考察对底层机制的理解。
2.1 核心接口一览
接口/类名 | 作用 |
---|---|
DriverManager | 管理数据库驱动,创建数据库连接 |
Connection | 表示数据库连接,支持事务管理 |
Statement | 用于执行静态 SQL 语句 |
PreparedStatement | 用于执行参数化 SQL,预编译,防止 SQL 注入 |
ResultSet | 查询结果集,可遍历读取每一行数据 |
2.2 PreparedStatement
与 Statement
的区别与优劣
虽然两者都能执行 SQL,但差距很大。
✅ Statement 示例(不推荐):
Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE name = '" + name + "'");
问题:
-
SQL 拼接麻烦
-
存在 SQL 注入风险
-
无法预编译,性能差
✅ PreparedStatement 示例(推荐):
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?"); ps.setString(1, name); ResultSet rs = ps.executeQuery();
优点:
-
预编译,提高性能(尤其在批量处理时)
-
防止 SQL 注入
-
代码结构清晰,参数类型更安全
🎯 面试官常问:你能讲讲 PreparedStatement 和 Statement 的区别吗?
回答思路:
安全性:PreparedStatement 能防 SQL 注入,Statement 不能;
性能:PreparedStatement 会预编译 SQL,提高效率;
可维护性:PreparedStatement 参数绑定清晰,SQL 拼接更安全简洁。
2.3 ResultSet 遍历与数据提取技巧
ResultSet
是 SQL 查询结果的封装对象,遍历它的方式如下:
while (rs.next()) {int id = rs.getInt("id");String name = rs.getString("name");// 还可以 rs.getDate(), rs.getDouble() 等 }
常见问题:
-
别忘了先
rs.next()
移动指针! -
字段类型要和数据库字段匹配,避免类型转换错误
2.4 JDBC 操作流程六步详解
这六步是经典套路,也是每一道 JDBC 面试题的“基本功”。
步骤 | 说明 | 示例 |
---|---|---|
1 | 加载驱动 | Class.forName("com.mysql.cj.jdbc.Driver"); (新版本可省略) |
2 | 获取连接 | DriverManager.getConnection(...) |
3 | 创建 SQL 对象 | PreparedStatement ps = conn.prepareStatement(sql); |
4 | 执行 SQL | ps.executeQuery() 或 ps.executeUpdate() |
5 | 处理结果 | 遍历 ResultSet |
6 | 释放资源 | 先关 ResultSet ,再关 Statement ,最后关 Connection |
⚠️ 注意资源释放顺序
资源释放顺序必须从“最小”向“最大”依次关闭:
try {// 使用资源 } finally {if (rs != null) rs.close();if (ps != null) ps.close();if (conn != null) conn.close(); }
或者使用 Java 7+ 的 try-with-resources:
try (Connection conn = ...;PreparedStatement ps = ...;ResultSet rs = ... ) {// 自动关闭资源 }
小结
-
JDBC 最核心的接口就是这一组:
Connection
、PreparedStatement
、ResultSet
; -
PreparedStatement 是面试最喜欢考的点,务必掌握其优势与使用方式;
-
理解 JDBC 的六步流程,是之后学习 Spring JDBC / MyBatis 的基础。
好的,下面是第三章的正文内容,聚焦于 JDBC 中的事务控制机制。这部分既是 JDBC 的重点,也与数据库事务知识(ACID)高度关联,是面试的常考模块。
三、JDBC 与事务控制
在真实项目中,我们对数据库的操作往往不是“查一下”这么简单。涉及增删改操作时,数据一致性就成了必须保障的底线。JDBC 提供了最原始但也是最灵活的事务控制能力。
3.1 JDBC 的事务控制机制
默认情况下,JDBC 的连接是自动提交(auto-commit)的,也就是说:
Connection conn = DriverManager.getConnection(...); Statement stmt = conn.createStatement(); stmt.executeUpdate("UPDATE account SET balance = balance - 100 WHERE id = 1"); // 语句执行后,立刻自动提交
但是问题来了:
-
如果你有多个语句组成一个业务逻辑,比如“转账”,就不能每句都立刻提交。
-
一旦其中一句失败,就无法回滚前面已提交的语句,这将导致数据不一致!
3.2 手动控制事务(推荐做法)
Connection conn = DriverManager.getConnection(...); try {conn.setAutoCommit(false); // 开启手动事务PreparedStatement ps1 = conn.prepareStatement("...");ps1.executeUpdate();PreparedStatement ps2 = conn.prepareStatement("...");ps2.executeUpdate();conn.commit(); // 所有操作成功后提交 } catch (Exception e) {conn.rollback(); // 有异常就回滚 } finally {conn.close(); }
✅ 这就是“原子性”的实现:
要么全部成功,要么全部失败,数据始终保持一致。
3.3 多事务嵌套的控制
JDBC 原生并不直接支持嵌套事务(不像 Spring 支持传播机制),但你可能会在同一个连接里“模拟嵌套行为”。
-
建议通过 分方法 + 同一个 Connection 传参 的方式,控制多个子事务。
-
不要在子方法中随意提交事务,统一由调用方决定 commit/rollback。
✅ 面试场景题:两个子模块都要操作数据库,一个失败另一个必须回滚,如何处理?
答:统一由上层控制事务边界,子模块只抛异常或返回状态,不主动 commit/rollback。
3.4 事务中断异常怎么处理?
一旦出错,必须 rollback()
,但很多人犯以下错误:
-
错误做法:
conn.setAutoCommit(false); stmt.executeUpdate(sql1); // 假如这里抛出异常…… stmt.executeUpdate(sql2); conn.commit(); // 永远不会执行
上面语句会卡在“执行了一半”,数据库被“脏改”。
-
正确做法: 把
commit()
和rollback()
都写在 try-catch 块中,保证异常后能回滚:try {conn.setAutoCommit(false);...conn.commit(); } catch (Exception e) {conn.rollback(); // 抓异常就回滚 }
✅ 面试陷阱题:JDBC 默认是自动提交的吗?
是的,JDBC 默认 autoCommit = true
。这意味着每条执行完的 SQL 都立即提交!
所以如果你执行一串更新语句而忘记手动关掉 autoCommit
,那就会:
-
成功一半、失败一半
-
无法回滚
-
数据出问题,后果极难排查
小结
-
JDBC 默认自动提交事务,一定要手动设置
autoCommit(false)
来控制事务边界。 -
commit()
和rollback()
要配对使用,并注意异常处理。 -
嵌套事务应避免在子方法中操作事务边界,统一由调用者负责。
-
事务控制是面试高频项,尤其在转账/订单/扣库存场景中反复考察。
当然,这一章会从最常用的 JDBC CURD 实战出发,给 Java 后端求职者和学习者一个“能直接搬到项目里”的落地模板,并结合典型业务场景和性能优化点。以下是正文内容:
四、JDBC 实战:增删改查 CURD 模板
JDBC 本质上就是“Java 写 SQL”,它提供了最基础、最直接的数据库访问能力。这一章我们就来搞定一件事:
如何用 JDBC 写出结构清晰、可维护、性能还不错的 CURD?
4.1 通用 CURD 模板封装思路
很多初学者写 JDBC 代码是这样的:
Connection conn = DriverManager.getConnection(...); PreparedStatement ps = conn.prepareStatement("INSERT INTO user (name) VALUES (?)"); ps.setString(1, "Tom"); ps.executeUpdate();
这没问题,但重复多了会显得啰嗦又难维护。我们应该做的是:抽出公共模板 + 封装通用工具类。
推荐封装结构:
public class JdbcUtil {public static Connection getConnection() { ... } // 获取连接public static void close(Connection conn, Statement stmt, ResultSet rs) { ... } // 关闭连接 }
4.2 批量插入与 addBatch()
使用
处理大量数据时,如果你还在用 for 循环一个个插入,那性能就彻底拉跨了。正确方式是用批处理:
Connection conn = JdbcUtil.getConnection(); String sql = "INSERT INTO user (name, age) VALUES (?, ?)"; PreparedStatement ps = conn.prepareStatement(sql);for (int i = 0; i < 1000; i++) {ps.setString(1, "User" + i);ps.setInt(2, 20 + i % 10);ps.addBatch(); // 批量添加if (i % 200 == 0) {ps.executeBatch(); // 每200条批量提交ps.clearBatch();} } ps.executeBatch(); // 最后不足200条的提交
✅ 这种方式比单条插入性能高 10 倍以上。
4.3 实战:实现一个简易 DAO 层(UserDAO)
这是面试中常见的问法:“你怎么设计 DAO 层?”
先定义实体类:
public class User {private int id;private String name;private int age;// getter / setter }
再封装 DAO:
public class UserDAO {public void insert(User user) throws SQLException {Connection conn = JdbcUtil.getConnection();String sql = "INSERT INTO user (name, age) VALUES (?, ?)";PreparedStatement ps = conn.prepareStatement(sql);ps.setString(1, user.getName());ps.setInt(2, user.getAge());ps.executeUpdate();JdbcUtil.close(conn, ps, null);}public List<User> findAll() throws SQLException {Connection conn = JdbcUtil.getConnection();String sql = "SELECT * FROM user";PreparedStatement ps = conn.prepareStatement(sql);ResultSet rs = ps.executeQuery();List<User> list = new ArrayList<>();while (rs.next()) {User u = new User();u.setId(rs.getInt("id"));u.setName(rs.getString("name"));u.setAge(rs.getInt("age"));list.add(u);}JdbcUtil.close(conn, ps, rs);return list;} }
✅ 你可以很容易扩展出 update
、delete
、findById
等方法,完全覆盖项目需求。
4.4 JDBC 与典型业务场景实战
✅ 用户登录
public boolean login(String username, String password) {String sql = "SELECT * FROM user WHERE username = ? AND password = ?";Connection conn = JdbcUtil.getConnection();PreparedStatement ps = conn.prepareStatement(sql);ps.setString(1, username);ps.setString(2, password);ResultSet rs = ps.executeQuery();return rs.next(); // 查到即登录成功 }
面试加分提示:真实系统中密码要加密(如 MD5、BCrypt),这里为简化省略。
✅ 注册校验用户名是否存在
public boolean exists(String username) {String sql = "SELECT COUNT(*) FROM user WHERE username = ?";PreparedStatement ps = JdbcUtil.getConnection().prepareStatement(sql);ps.setString(1, username);ResultSet rs = ps.executeQuery();if (rs.next()) {return rs.getInt(1) > 0;}return false; }
✅ 分页查询
public List<User> findByPage(int page, int size) {int offset = (page - 1) * size;String sql = "SELECT * FROM user ORDER BY id DESC LIMIT ?, ?";PreparedStatement ps = JdbcUtil.getConnection().prepareStatement(sql);ps.setInt(1, offset);ps.setInt(2, size);ResultSet rs = ps.executeQuery();// 同上遍历 rs 并封装 User }
小结
-
JDBC 虽然底层,但是真实面试中很多问题都来自它。
-
封装通用模板、精通批处理、了解业务常见场景,是你把 JDBC 用“好”的关键。
-
如果你能写出一个简洁、扩展性强的 DAO 层,就比大多数人强很多了。
当然,下面是这一部分的正文内容,依旧保持实用性 + 面试导向 + 易落地的风格:
五、JDBC 性能优化与连接池技术
很多人学 JDBC 时最大的误区是:以为写完 Connection conn = DriverManager.getConnection(...)
就完事了。
错!性能问题往往就藏在这句代码里。
这章核心目标:搞清楚 JDBC 的性能瓶颈在哪?连接池怎么选、怎么配、怎么调?
5.1 JDBC 性能瓶颈在哪?
打开数据库连接是非常昂贵的操作(涉及三次握手、权限认证、内存资源分配等),而且频繁开关连接还会拖垮数据库。
👇 举个例子:
for (int i = 0; i < 1000; i++) {Connection conn = DriverManager.getConnection(...); // ❌ 慢 + 占资源PreparedStatement ps = conn.prepareStatement(...);...conn.close(); }
即使你在每次操作后都关闭连接,也会因为频繁连接 + 关闭而严重拖慢性能(甚至打爆连接池、耗尽数据库连接数)。
5.2 为什么要用连接池?
连接池的核心思路就是:
提前准备好一批连接,程序要用的时候“借”,用完再“还”。
这样就避免了频繁创建/销毁连接的问题,显著提高性能。
连接池带来的好处:
-
✅ 避免频繁创建连接(重用连接)
-
✅ 控制连接数量,避免资源泄露
-
✅ 提供连接监控、超时检测、慢查询告警等能力
5.3 常见连接池技术对比
名称 | 特点 | 是否推荐 |
---|---|---|
C3P0 | 老牌连接池,功能齐全,但性能较差 | ❌ 过时 |
DBCP | Apache 提供,轻量但稳定性一般 | ⚠️ 一般 |
HikariCP | Spring Boot 默认连接池,极致性能 | ✅ 推荐 |
Druid | 阿里出品,功能丰富、监控强大,但稍重 | ✅ 大厂常用 |
面试问你连接池用哪个?——Spring Boot 默认用的是 HikariCP,Druid 常见于业务复杂系统。
5.4 HikariCP 实践:配置、监控、调优
HikariCP 是目前性能最强、最现代的连接池实现,Spring Boot 从 2.x 开始就默认采用它。
✅ HikariCP 基本配置(application.yml)
spring:datasource:url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTCusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverhikari:minimum-idle: 5maximum-pool-size: 15idle-timeout: 600000max-lifetime: 1800000connection-timeout: 30000
参数说明:
-
maximum-pool-size
: 最大连接数,决定并发能力,可根据服务器核数适配(CPU 核数 * 2) -
idle-timeout
: 空闲连接最大存活时间,建议10分钟 -
max-lifetime
: 连接最大生命周期,防止被数据库强踢 -
connection-timeout
: 获取连接最大等待时间
5.5 如何监控连接池?
如果你用的是 Druid,可以直接访问内置的监控界面:
spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=123456
访问 http://localhost:8080/druid
即可查看实时连接数、SQL 执行时间等信息。
而 HikariCP 可以通过 Micrometer + Actuator 监控:
<dependency><groupId>io.micrometer</groupId><artifactId>micrometer-core</artifactId> </dependency>
加上 Spring Boot Actuator 之后,访问 /actuator/metrics/hikaricp.connections.active
等指标即可。
✅ 总结一句话:
-
连接池是 JDBC 性能优化的第一要素
-
项目小就用 HikariCP(默认、快),项目复杂就用 Druid(功能强)
-
永远不要每次操作都新建一个
Connection
六、JDBC 与数据库安全
安全永远是开发中不能忽视的问题,尤其是涉及数据库访问时。很多“老掉牙”的漏洞,其实至今依然频繁出现——比如最常见的:SQL 注入。
这一章,我们就从 JDBC 层面,深入聊一聊:
-
JDBC 怎么防 SQL 注入?
-
PreparedStatement 真的是万能的吗?
-
代码和数据库分别该怎么做好权限隔离?
6.1 防止 SQL 注入的最佳实践
什么是 SQL 注入?
SQL 注入(SQL Injection)指的是攻击者通过构造恶意输入,干扰 SQL 查询结构,从而绕过验证、窃取数据甚至破坏数据的行为。
👇 举个“死亡写法”的例子:
String username = request.getParameter("username"); String password = request.getParameter("password");String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);
如果攻击者输入的是:
username = admin' -- password = 随便
最终拼接出的 SQL 会变成:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '随便'
结果:整个 password 条件被注释,直接登录成功!
6.2 如何防御:使用 PreparedStatement
正确的写法如下 👇:
String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery();
PreparedStatement 会自动将输入进行参数预编译与转义,彻底避免注入漏洞。
✅ 好处:
-
自动处理特殊字符(如
'
、"
等) -
提前编译 SQL,提高性能
-
完全阻断 SQL 注入风险
6.3 PreparedStatement 有局限吗?
是的,它并非万能,尤其在动态构建 SQL 结构时。
例如下面这种“模糊查询 + 动态字段”的写法:
String column = request.getParameter("sortBy"); // 危险! String sql = "SELECT * FROM products ORDER BY " + column;
攻击者传入 sortBy=1 desc; DROP TABLE products;
,那你基本就可以下班了。
应对策略:
-
永远不要让用户控制 SQL 的结构(如字段名、表名、order by 等)
-
对字段名做白名单过滤(限定只能是
id
、name
、price
等)
6.4 数据库权限控制(代码层 + 数据库层双保险)
除了写代码要安全,还要从数据库账号本身“限权”:
✅ 原则:最小权限原则(Least Privilege)
-
业务用账号不能有
DROP
、ALTER
等高权限 -
管理账号仅供管理员使用,不放进代码里
-
分模块设置账号:只读账号 / 读写账号 / 审计账号
👇 代码层也可通过 @Secured
、@PreAuthorize
做权限控制:
@PreAuthorize("hasRole('ADMIN')") public void deleteUser(...) {... }
✅ 总结一句话:
-
永远用
PreparedStatement
,别拼接 SQL -
防注入的核心:用户输入不能影响 SQL 结构
-
数据库和代码都要限权,不留后门
如果你准备面试,这一章很有可能就是“开胃题”,比如:
面试官:你知道 PreparedStatement 和 Statement 有什么区别吗? 你可以直接说:“PreparedStatement 是防注入的核心机制,因为……”
七、JDBC 与框架集成:从原始连接到高效实战
JDBC 是 Java 世界访问数据库的“底层接口”,但如果你真的在企业项目中还在手写一堆 try-catch-finally
,那只能说——你该升级了。
所以这一章,我们就来聊聊:
-
为什么 Spring JDBC 更“香”?
-
JdbcTemplate 是怎么帮我们简化代码的?
-
什么时候应该用 MyBatis?JDBC 和 ORM 怎么选?
-
JDBC 在实际项目中还有哪些典型用法?
7.1 Spring JDBC 简介:让底层 JDBC 更“丝滑”
Spring Framework 为原生 JDBC 提供了一个更轻量的封装层:Spring JDBC,也称 JdbcTemplate。
它解决的核心痛点就是:
原生 JDBC 的痛点 | Spring JDBC 的优化 |
---|---|
代码冗长、重复 | 简洁模板式写法 |
异常处理复杂 | 统一包装成 DataAccessException |
资源释放容易写漏(如关闭) | 自动关闭 Connection、Statement、ResultSet |
参数设置容易出错 | 自动填充参数 |
简单理解就是:Spring JDBC 是对原生 JDBC 的“工具化封装”,让你更专注于业务逻辑,而不是底层流程。
7.2 JdbcTemplate 使用指南
Spring 提供的 JdbcTemplate
是 Spring JDBC 的核心工具类。
典型使用方式:
@Autowired private JdbcTemplate jdbcTemplate;public List<User> getUsers() {String sql = "SELECT * FROM users";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); }
参数绑定 + 插入示例:
String sql = "INSERT INTO users(name, age) VALUES (?, ?)"; jdbcTemplate.update(sql, "Tom", 25);
查询一个值(比如总数):
String sql = "SELECT COUNT(*) FROM users"; int count = jdbcTemplate.queryForObject(sql, Integer.class);
是不是比原始的 Statement
+ ResultSet
干净太多了?
7.3 与 MyBatis 的对比:什么时候用 JDBC,什么时候用 ORM?
对比项 | JdbcTemplate | MyBatis |
---|---|---|
性质 | 半自动(基于 SQL) | 全自动映射 ORM 框架 |
学习曲线 | 较低 | 中等(XML / 注解配置) |
灵活性 | 更贴近原生 SQL | 可扩展性好,支持 SQL 映射、插件机制 |
适合场景 | 简单 CRUD、批处理 | 复杂业务、多表关联、动态 SQL |
与 JDBC 的关系 | 基于 JDBC 封装 | 底层仍然使用 JDBC |
简单说:
-
小型服务 / 简单 CRUD:
JdbcTemplate
非常轻量高效; -
中大型项目 / 动态查询多:
MyBatis
更灵活,也更可维护; -
纯 ORM 实体映射需求重:考虑
Hibernate
但要小心性能坑。
7.4 企业项目中 JDBC 的典型应用场景
JDBC 本质是“数据库访问协议”,即使用了框架,底层还是 JDBC。
常见使用方式:
-
批处理任务(如 Spring Batch 中的数据库写入)
-
后台脚本 / 任务系统 / 数据导出
-
与传统系统对接时(只支持 JDBC 驱动)
-
需要极致性能优化的场景(手动控制连接与执行)
在这些“贴底层”的场景里,JdbcTemplate
+ 手动 SQL 仍然是非常可靠的选择。
✅ 小结:框架是帮你提效,不是让你忘了底层
-
原生 JDBC 太冗长,Spring JDBC 提供高效封装;
-
JdbcTemplate
是 JDBC 入门的最佳实战工具; -
MyBatis / Hibernate 适合复杂业务场景,但 JDBC 更轻、更快;
-
面试常问:“JDBC 和 ORM 哪个好?”别说“我都用”,说“按需选型”。
八、常见问题与面试题精选
JDBC 是 Java 与数据库之间的桥梁,但真正面试时,考察的不是你记了多少 API,而是你对连接池、事务控制、封装思想的理解是否深入。
这一节我们拆解 4 个高频面试方向,帮你建立系统化认知:
1️⃣ JDBC 与连接池的面试高频题解析
问题 1:为什么 JDBC 不建议频繁创建和关闭连接?
JDBC 的
Connection
对象是重量级资源,底层实际是“网络 TCP 连接 + 数据库 session”。 每次DriverManager.getConnection()
都会发起一次物理连接,成本高昂。
面试建议答法:
-
每次连接的建立和销毁会造成性能瓶颈;
-
在高并发环境下更容易造成数据库连接资源耗尽;
-
正确做法是使用连接池(如 Druid、HikariCP)复用连接。
问题 2:你了解哪些常见连接池?区别在哪?
连接池 | 特点 |
---|---|
DBCP | 早期 Spring 默认使用,已逐渐淘汰 |
C3P0 | 配置简单,性能一般 |
Druid | 阿里出品,监控强大,使用广泛 |
HikariCP | 性能最佳,Spring Boot 默认连接池 |
建议重点掌握 Druid(监控强)和 HikariCP(速度快)。
2️⃣ JDBC 如何实现事务控制?嵌套事务如何处理?
问题 3:JDBC 默认是自动提交事务的吗?如何手动控制?
是的,JDBC 默认执行完每条 SQL 就自动提交。 想手动控制事务,需要关闭自动提交模式:
conn.setAutoCommit(false); // 开启手动提交模式 try {// 执行业务 SQLconn.commit(); } catch (Exception e) {conn.rollback(); }
问题 4:JDBC 支持嵌套事务吗?怎么处理?
原生 JDBC 不支持嵌套事务。你只能通过逻辑封装来模拟“嵌套”控制,比如利用保存点(SavePoint):
Connection conn = getConnection(); conn.setAutoCommit(false); Savepoint sp = conn.setSavepoint();try {// 第一个事务逻辑// 第二段逻辑出错conn.rollback(sp); // 回滚到 savepointconn.commit(); } catch (Exception e) {conn.rollback(); }
面试建议回答:
JDBC 不直接支持嵌套事务,但可通过
Savepoint
模拟部分事务回滚能力。
3️⃣ 如何解决 JDBC 中连接泄漏问题?
连接泄漏 = 获取了连接但没有释放,长期积压导致连接池耗尽、系统崩溃。
常见原因:
-
忘记调用
conn.close()
; -
异常发生后未进入
finally
释放连接; -
多线程并发下 Connection 被覆盖。
解决方案:
-
强制使用
try-with-resources
(Java 7 起)自动释放资源:
try (Connection conn = getConnection()) {// 操作 } // 自动 close()
-
使用连接池 + 配置连接超时、空闲检测;
-
配合 Druid 监控分析连接使用情况。
4️⃣ 面试官提问:“如果让你封装一个通用 JDBC 工具类,你怎么设计?”
这个问题非常经典,既考察编码能力,也考察抽象能力。
答题思路:
我会从复用性、可扩展性、安全性角度出发,封装以下功能模块:
-
统一管理连接池数据源(可注入 Druid 或 Hikari);
-
提供通用方法(如
queryList()
、update()
、queryOne()
); -
使用泛型 + RowMapper 回调封装结果映射逻辑;
-
使用 try-with-resources 避免资源泄漏;
-
异常统一封装为 RuntimeException,避免调用者处理繁琐 SQL 异常。
代码结构示意:
public class JdbcUtil {private static DataSource ds = ...;public static <T> List<T> queryList(String sql, RowMapper<T> mapper, Object... params) {try (Connection conn = ds.getConnection();PreparedStatement ps = conn.prepareStatement(sql)) {// 填充参数 + 执行 + 映射}} }
✅ 小结
问题维度 | 面试关键点 |
---|---|
连接池原理 | 性能优化、复用连接、监控配置 |
事务控制 | 手动控制、回滚、嵌套模拟 SavePoint |
连接泄漏问题 | try-with-resources、连接池配置、监控排查 |
封装思想 | 泛型、异常处理、模板方法、低耦合可维护的代码设计思维 |