JDBC 零基础入门到实战:手把手教你用 Java 操作数据库 ☕️

JDBC(Java Database Connectivity)是 Java 语言操作数据库的标准 API,理解和掌握 JDBC 是每一个 Java 后端开发者的必修课。本文将从零开始,系统讲解 JDBC 的使用方法、核心概念、进阶技巧以及最佳实践,帮助你真正做到理论与实战相结合!💪


📚 目录导航


一、JDBC 概述:什么是 JDBC?

1.1 JDBC 的诞生

在 JDBC 诞生之前,Java 程序想要操作不同的数据库(比如 MySQL、Oracle、SQL Server),需要针对每种数据库编写不同的代码。这是因为每种数据库都有自己独特的通信协议和数据操作语言。

JDBC 的出现解决了这个痛点——它提供了一套统一的数据库操作 API,开发者只需要学会这一套 API,就能操作任何支持 JDBC 的数据库。

1.2 JDBC 的工作原理

JDBC 采用的是分层架构,它位于应用程序和数据库之间,充当桥梁的角色:

各层职责:

层级 组件 职责
应用层 我们的 Java 代码 编写业务逻辑,调用 JDBC API
API 层 java.sql.* 提供统一接口,如 DriverManagerConnectionStatement
驱动管理层 DriverManager 加载驱动,建立连接
驱动层 数据库厂商提供的驱动 JAR 实现 JDBC 接口,负责与数据库通信

1.3 JDBC 驱动类型

JDBC 将数据库厂商提供的驱动分为四类:

类型 说明 示例
Type 1 ODBC 桥接驱动,需要本地 ODBC 支持 JDBC-ODBC Bridge(已淘汰)
Type 2 本地 API 驱动,部分 Java,部分 Native Oracle Call Interface
Type 3 网络协议驱动,中间件转换 Java Applet 连接中间件
Type 4 纯 Java 驱动,直接与数据库通信 MySQL Connector/J、PostgreSQL JDBC Driver

💡 推荐:日常开发首选 Type 4 驱动(纯 Java 实现),因为它具有平台无关性,只需引入 JAR 包即可使用。

1.4 JDBC 的优势

  • 平台无关性:一次编写,到处运行
  • 数据库无关性:学会一套 API,操作多种数据库
  • 标准统一:Sun 公司制定,所有数据库厂商遵循
  • 易于学习:API 设计简洁,概念清晰

二、JDBC 驱动的加载与连接

2.1 环境准备

在开始之前,你需要准备好以下环境:

  1. **JDK 1.8+**:确保 Java 开发环境已配置
  2. MySQL 数据库:本地或远程 MySQL 服务
  3. MySQL JDBC 驱动mysql-connector-java-8.0.x.jar
  4. IDE:IntelliJ IDEA 或 Eclipse

📦 驱动获取:从 MySQL 官网 下载对应版本的 Connector/J JAR 包。

2.2 创建数据库和表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 创建数据库
CREATE DATABASE IF NOT EXISTS jdbc_demo DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

USE jdbc_demo;

-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '密码(加密存储)',
email VARCHAR(100) COMMENT '邮箱',
status TINYINT DEFAULT 1 COMMENT '状态:1-正常,0-禁用',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 插入测试数据
INSERT INTO users (username, password, email) VALUES
('admin', '$2a$10$xxxx', 'admin@example.com'),
('test', '$2a$10$yyyy', 'test@example.com');

2.3 加载 JDBC 驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方式一:Class.forName() 加载驱动(JDBC 3.0 之前的标准写法)
// 驱动类会自动向 DriverManager 注册
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

// 方式二:无需手动加载(JDBC 4.0+)
// 从 MySQL Connector/J 8.0 开始,只要驱动 JAR 在 classpath 中,
// 就会自动加载,无需 Class.forName()
// 但为了兼容性和明确性,很多项目仍保留这一行

// 方式三:使用 ServiceLoader 机制手动加载
// MySQL 驱动的 META-INF/services/java.sql.Driver 文件中声明了驱动类
// Java 程序启动时会自动扫描并加载

2.4 建立数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JdbcConnection {

public static void main(String[] args) {
// 数据库连接参数
String url = "jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8";
String username = "root";
String password = "your_password";

Connection connection = null;

try {
// 注册驱动(可选,JDBC 4.0+ 自动加载)
Class.forName("com.mysql.cj.jdbc.Driver");

// 建立连接
connection = DriverManager.getConnection(url, username, password);

// 判断连接是否成功
if (connection != null) {
System.out.println("✅ 数据库连接成功!");
System.out.println("连接对象:" + connection);
System.out.println("连接 URL:" + connection.getMetaData().getURL());
System.out.println("数据库产品:" + connection.getMetaData().getDatabaseProductName());
}

} catch (ClassNotFoundException e) {
System.out.println("❌ 驱动类加载失败:" + e.getMessage());
} catch (SQLException e) {
System.out.println("❌ 数据库连接失败:" + e.getMessage());
} finally {
// 关闭连接,释放资源
if (connection != null) {
try {
connection.close();
System.out.println("🔌 连接已关闭");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

2.5 JDBC URL 详解

JDBC URL 是用于定位数据库资源的字符串,格式如下:

1
jdbc:mysql://主机名:端口号/数据库名?参数1=值1&参数2=值2
参数 说明 示例
useSSL 是否使用 SSL 连接 true / false
serverTimezone 服务器时区(必须设置) Asia/Shanghai / UTC
characterEncoding 字符编码 utf8 / utf8mb4
useUnicode 是否使用 Unicode true
allowPublicKeyRetrieval 允许公钥检索(MySQL 8.0+) true

⚠️ 常见错误:如果使用 MySQL 8.0+ 且未设置 serverTimezone,可能报错 The server time zone value 'xxx' is unrecognized


三、JDBC API 核心对象

3.1 四大核心对象概述

JDBC 的核心操作离不开四个关键对象,它们之间的关系如下:

对象 类型 作用
DriverManager 驱动管理器 加载驱动、建立连接
Connection 接口 表示数据库连接,负责事务管理
Statement 接口 执行 SQL 语句
PreparedStatement 接口 预编译 SQL,防止 SQL 注入
ResultSet 接口 封装查询结果集

3.2 DriverManager 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// DriverManager 的核心方法

// 1. 建立连接(最常用)
Connection conn = DriverManager.getConnection(url, username, password);

// 2. 建立连接(简化写法,密码直接拼在 URL 中)
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/jdbc_demo?user=root&password=your_password"
);

// 3. 设置连接超时时间(单位:秒)
DriverManager.setLoginTimeout(10);

// 4. 获取可用的驱动列表
java.util.Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
System.out.println("已注册的驱动:" + driver.getClass().getName());
}

3.3 Connection 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Connection 接口的核心方法

// 1. 创建 Statement 对象
Statement stmt = connection.createStatement();

// 2. 创建预编译 SQL 的 PreparedStatement 对象
PreparedStatement ps = connection.prepareStatement(sql);

// 3. 设置事务提交方式
connection.setAutoCommit(false); // 手动提交事务
connection.setAutoCommit(true); // 自动提交(默认)

// 4. 提交事务
connection.commit();

// 5. 回滚事务
connection.rollback();

// 6. 创建保存点(支持部分回滚)
Savepoint savepoint = connection.setSavepoint("sp1");
connection.rollback(savepoint);

// 7. 获取数据库元数据
DatabaseMetaData metaData = connection.getMetaData();
String dbName = metaData.getDatabaseProductName();
String dbVersion = metaData.getDatabaseProductVersion();

// 8. 判断是否连接到数据库
System.out.println("是否有效:" + connection.isValid(5));

// 9. 设置只读模式(优化性能)
connection.setReadOnly(true);

// 10. 设置事务隔离级别
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

// 11. 关闭连接
connection.close();

3.4 Statement 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Statement 用于执行静态 SQL 语句

// 1. 执行查询(返回 ResultSet)
String sql1 = "SELECT * FROM users WHERE id = 1";
ResultSet rs = statement.executeQuery(sql1);

// 2. 执行增删改(返回影响的行数)
String sql2 = "INSERT INTO users (username, password, email) VALUES ('test', '123456', 'test@example.com')";
int rows = statement.executeUpdate(sql2);
System.out.println("插入了 " + rows + " 行数据");

// 3. 执行任意 SQL(返回 boolean,表示是否有 ResultSet)
String sql3 = "SELECT * FROM users";
boolean hasResult = statement.execute(sql3);
System.out.println("是否有结果集:" + hasResult);

// 4. 批量执行 SQL
statement.addBatch("INSERT INTO users VALUES (NULL, 'user1', 'pass1', 'user1@example.com')");
statement.addBatch("INSERT INTO users VALUES (NULL, 'user2', 'pass2', 'user2@example.com')");
statement.addBatch("INSERT INTO users VALUES (NULL, 'user3', 'pass3', 'user3@example.com')");
int[] batchResults = statement.executeBatch(); // 返回每条 SQL 影响行数的数组
statement.clearBatch(); // 清空批次

// 5. 获取生成的主键(MySQL 有效)
String sql4 = "INSERT INTO users (username, password, email) VALUES ('test', 'pass', 'test@example.com')";
statement.executeUpdate(sql4, Statement.RETURN_GENERATED_KEYS);
ResultSet keys = statement.getGeneratedKeys();
if (keys.next()) {
long generatedId = keys.getLong(1);
System.out.println("生成的主键:" + generatedId);
}

// 6. 获取 ResultSet 的元数据
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
System.out.println("第 " + i + " 列名:" + metaData.getColumnName(i));
System.out.println("第 " + i + " 列类型:" + metaData.getColumnTypeName(i));
}

3.5 ResultSet 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// ResultSet 封装了查询结果,可以通过游标遍历

// 1. 基本遍历(next() 方法移动游标)
while (rs.next()) {
// 获取数据(方式一:按列名)
Long id = rs.getLong("id");
String username = rs.getString("username");
String email = rs.getString("email");

// 获取数据(方式二:按列索引,从 1 开始)
Long id2 = rs.getLong(1);
String username2 = rs.getString(2);

System.out.println(id + " - " + username + " - " + email);
}

// 2. 获取不同类型的数据
rs.getInt("id"); // 整数
rs.getLong("id"); // 长整数
rs.getDouble("balance"); // 双精度浮点数
rs.getBigDecimal("amt"); // BigDecimal 精确小数
rs.getBoolean("status"); // 布尔值
rs.getDate("created_at"); // 日期(java.sql.Date)
rs.getTime("login_time"); // 时间(java.sql.Time)
rs.getTimestamp("updated_at"); // 时间戳
rs.getString("content"); // 字符串
rs.getBytes("file_data"); // 字节数组
rs.getBlob("avatar"); // 二进制大对象
rs.getClob("description"); // 字符大对象

// 3. 判断是否有下一行
if (rs.next()) {
// 有数据
} else {
// 无数据
}

// 4. 判断游标是否在第一行之前(beforeFirst)之后(afterLast)
rs.beforeFirst(); // 移动到第一行之前
rs.afterLast(); // 移动到最后一行之后
rs.first(); // 移动到第一行
rs.last(); // 移动到最后一行
rs.absolute(5); // 移动到第 5 行
rs.relative(2); // 相对移动 2 行

// 5. 获取结果集信息
ResultSetMetaData metaData = rs.getMetaData();
System.out.println("总列数:" + metaData.getColumnCount());
System.out.println("第一列名称:" + metaData.getColumnName(1));
System.out.println("第一列类型:" + metaData.getColumnTypeName(1));

// 6. 判断 ResultSet 是否可滚动(需要创建 Statement 时指定)
Statement stmt = connection.createStatement(
ResultSet.TYPE_FORWARD_ONLY, // 游标类型:只能前进
ResultSet.CONCUR_READ_ONLY // 并发性:只读
);

Statement stmtScroll = connection.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, // 游标类型:可滚动,不敏感
ResultSet.CONCUR_UPDATABLE // 并发性:可更新
);

// 7. 更新 ResultSet 中的数据(可更新模式下)
rs.absolute(1);
rs.updateString("email", "new_email@example.com");
rs.updateRow(); // 将更新同步到数据库

// 8. 插入新行
rs.moveToInsertRow(); // 移动到插入行
rs.updateString("username", "new_user");
rs.updateString("password", "new_pass");
rs.updateString("email", "new_user@example.com");
rs.insertRow(); // 执行插入

四、CRUD 实战:增删改查

4.1 项目结构设计

4.2 创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.example.entity;

import java.util.Date;

/**
* 用户实体类
*/
public class User {

private Long id;
private String username;
private String password;
private String email;
private Integer status;
private Date createdAt;
private Date updatedAt;

// 构造函数
public User() {
}

public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
this.status = 1;
}

// Getter 和 Setter 方法
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public Integer getStatus() {
return status;
}

public void setStatus(Integer status) {
this.status = status;
}

public Date getCreatedAt() {
return createdAt;
}

public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}

public Date getUpdatedAt() {
return updatedAt;
}

public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

4.3 查询单个对象(SELECT WHERE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.example.dao;

import com.example.entity.User;
import com.example.util.JdbcUtil;

import java.sql.*;

/**
* 用户 DAO 层:负责用户表的数据操作
*/
public class UserDao {

/**
* 根据 ID 查询用户
*/
public User findById(Long id) {
// SQL 语句
String sql = "SELECT * FROM users WHERE id = ?";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
// 获取连接
conn = JdbcUtil.getConnection();

// 预编译 SQL
ps = conn.prepareStatement(sql);
ps.setLong(1, id);

// 执行查询
rs = ps.executeQuery();

// 遍历结果集
if (rs.next()) {
return extractUser(rs);
}

} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
JdbcUtil.close(rs, ps, conn);
}

return null;
}

/**
* 根据用户名查询用户
*/
public User findByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = ?";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1, username);

rs = ps.executeQuery();

if (rs.next()) {
return extractUser(rs);
}

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(rs, ps, conn);
}

return null;
}

/**
* 从 ResultSet 中提取 User 对象
*/
private User extractUser(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setStatus(rs.getInt("status"));
user.setCreatedAt(rs.getTimestamp("created_at"));
user.setUpdatedAt(rs.getTimestamp("updated_at"));
return user;
}
}

4.4 查询集合(SELECT LIST)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* 查询所有用户
*/
public List<User> findAll() {
String sql = "SELECT * FROM users ORDER BY id ASC";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);

rs = ps.executeQuery();

// 创建返回的列表
List<User> users = new ArrayList<>();

// 遍历结果集
while (rs.next()) {
users.add(extractUser(rs));
}

return users;

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(rs, ps, conn);
}

return Collections.emptyList();
}

/**
* 分页查询用户
*/
public List<User> findByPage(int pageNum, int pageSize) {
String sql = "SELECT * FROM users ORDER BY id ASC LIMIT ?, ?";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);

// 计算分页偏移量
int offset = (pageNum - 1) * pageSize;
ps.setInt(1, offset);
ps.setInt(2, pageSize);

rs = ps.executeQuery();

List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(extractUser(rs));
}

return users;

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(rs, ps, conn);
}

return Collections.emptyList();
}

/**
* 条件查询:查询启用状态的用户
*/
public List<User> findByStatus(int status) {
String sql = "SELECT * FROM users WHERE status = ? ORDER BY created_at DESC";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, status);

rs = ps.executeQuery();

List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(extractUser(rs));
}

return users;

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(rs, ps, conn);
}

return Collections.emptyList();
}

4.5 插入数据(INSERT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* 添加用户
*/
public int insert(User user) {
// 注意:密码在实际应用中应该加密存储
String sql = "INSERT INTO users (username, password, email, status) VALUES (?, ?, ?, ?)";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

// 设置参数
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getEmail());
ps.setInt(4, user.getStatus() != null ? user.getStatus() : 1);

// 执行插入
int rows = ps.executeUpdate();

// 获取自增主键
if (rows > 0) {
rs = ps.getGeneratedKeys();
if (rs.next()) {
long generatedId = rs.getLong(1);
user.setId(generatedId);
System.out.println("✅ 生成的主键 ID:" + generatedId);
}
}

return rows;

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(rs, ps, conn);
}

return 0;
}

/**
* 批量插入用户
*/
public int batchInsert(List<User> users) {
String sql = "INSERT INTO users (username, password, email, status) VALUES (?, ?, ?, ?)";

Connection conn = null;
PreparedStatement ps = null;

try {
conn = JdbcUtil.getConnection();

// 关闭自动提交,开启手动事务
conn.setAutoCommit(false);

ps = conn.prepareStatement(sql);

for (User user : users) {
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getEmail());
ps.setInt(4, user.getStatus() != null ? user.getStatus() : 1);
ps.addBatch(); // 添加到批次
}

// 批量执行
int[] results = ps.executeBatch();

// 提交事务
conn.commit();

// 计算总影响行数
int totalRows = 0;
for (int result : results) {
if (result >= 0) {
totalRows += result;
}
}

return totalRows;

} catch (SQLException e) {
// 发生异常,回滚事务
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
JdbcUtil.close(null, ps, conn);
}

return 0;
}

4.6 更新数据(UPDATE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* 更新用户信息
*/
public int update(User user) {
String sql = "UPDATE users SET username = ?, password = ?, email = ?, status = ? WHERE id = ?";

Connection conn = null;
PreparedStatement ps = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);

ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getEmail());
ps.setInt(4, user.getStatus());
ps.setLong(5, user.getId());

int rows = ps.executeUpdate();

System.out.println("✅ 更新了 " + rows + " 行数据");
return rows;

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(null, ps, conn);
}

return 0;
}

/**
* 更新用户状态
*/
public int updateStatus(Long userId, int status) {
String sql = "UPDATE users SET status = ? WHERE id = ?";

Connection conn = null;
PreparedStatement ps = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);

ps.setInt(1, status);
ps.setLong(2, userId);

return ps.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(null, ps, conn);
}

return 0;
}

/**
* 批量更新用户状态
*/
public int batchUpdateStatus(List<Long> userIds, int status) {
if (userIds == null || userIds.isEmpty()) {
return 0;
}

// 构建动态 SQL:UPDATE users SET status = ? WHERE id IN (?, ?, ?)
StringBuilder sqlBuilder = new StringBuilder("UPDATE users SET status = ? WHERE id IN (");
for (int i = 0; i < userIds.size(); i++) {
sqlBuilder.append("?");
if (i < userIds.size() - 1) {
sqlBuilder.append(",");
}
}
sqlBuilder.append(")");

Connection conn = null;
PreparedStatement ps = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sqlBuilder.toString());

ps.setInt(1, status);
for (int i = 0; i < userIds.size(); i++) {
ps.setLong(i + 2, userIds.get(i));
}

return ps.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(null, ps, conn);
}

return 0;
}

4.7 删除数据(DELETE)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
* 根据 ID 删除用户
*/
public int deleteById(Long id) {
String sql = "DELETE FROM users WHERE id = ?";

Connection conn = null;
PreparedStatement ps = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setLong(1, id);

int rows = ps.executeUpdate();

System.out.println("✅ 删除了 " + rows + " 行数据");
return rows;

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(null, ps, conn);
}

return 0;
}

/**
* 批量删除用户
*/
public int batchDelete(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return 0;
}

StringBuilder sqlBuilder = new StringBuilder("DELETE FROM users WHERE id IN (");
for (int i = 0; i < ids.size(); i++) {
sqlBuilder.append("?");
if (i < ids.size() - 1) {
sqlBuilder.append(",");
}
}
sqlBuilder.append(")");

Connection conn = null;
PreparedStatement ps = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sqlBuilder.toString());

for (int i = 0; i < ids.size(); i++) {
ps.setLong(i + 1, ids.get(i));
}

return ps.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(null, ps, conn);
}

return 0;
}

/**
* 删除所有用户(危险操作!)
*/
public int deleteAll() {
String sql = "DELETE FROM users";

Connection conn = null;
PreparedStatement ps = null;

try {
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement(sql);

return ps.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(null, ps, conn);
}

return 0;
}

五、预编译 SQL 与防止 SQL 注入

5.1 SQL 注入攻击原理

SQL 注入是 Web 应用中最常见的安全漏洞之一。让我们先看看它是如何发生的:

5.2 SQL 注入示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ 危险!使用 Statement 直接拼接 SQL
public User findByUsernameDangerous(String username) {
// 恶意输入:username = "admin' OR '1'='1"
String sql = "SELECT * FROM users WHERE username = '" + username + "'";

// 实际执行的 SQL:
// SELECT * FROM users WHERE username = 'admin' OR '1'='1'
// 这会匹配所有用户,攻击者可能借此登录系统!

// 如果用户名是:'; DROP TABLE users; --
// 实际执行的 SQL:
// SELECT * FROM users WHERE username = ''; DROP TABLE users; --'
// 这会删除整个 users 表!
}

5.3 预编译 SQL 防注入

1
2
3
4
5
6
7
8
9
10
// ✅ 安全!使用 PreparedStatement 预编译 SQL
public User findByUsernameSafe(String username) {
// PreparedStatement 会对特殊字符转义
// 输入:admin' OR '1'='1
// 会被当作普通字符串处理,不会影响 SQL 结构
String sql = "SELECT * FROM users WHERE username = ?";

// 参数绑定后,即使输入包含 SQL 语句,也不会被执行
// 最终只会查找用户名为 "admin' OR '1'='1" 的用户(不存在)
}

5.4 PreparedStatement 优势总结

特性 Statement PreparedStatement
SQL 拼接 手动拼接字符串 参数绑定 ? 占位符
SQL 注入 ❌ 容易攻击 ✅ 安全防护
性能 每次编译新 SQL 预编译,重复执行效率高
可读性 拼接复杂,难维护 代码清晰,参数分明
类型安全 全部 String 支持多种数据类型绑定

5.5 预编译 SQL 参数绑定详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String sql = "INSERT INTO users (username, password, email, status, balance, created_at) " +
"VALUES (?, ?, ?, ?, ?, ?)";

PreparedStatement ps = conn.prepareStatement(sql);

// 设置参数(按问号顺序,从 1 开始)
ps.setString(1, "testuser"); // 字符串
ps.setString(2, "hashed_password"); // 字符串
ps.setString(3, "test@example.com"); // 字符串
ps.setInt(4, 1); // 整数
ps.setBigDecimal(5, new BigDecimal("100.50")); // BigDecimal
ps.setTimestamp(6, new Timestamp(System.currentTimeMillis())); // 时间戳

// 如果某个字段允许 NULL
ps.setNull(4, Types.INTEGER); // 设置为 NULL,并指定 SQL 类型

// 批量设置参数
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getEmail());
ps.setInt(4, user.getStatus());
ps.addBatch();
}
ps.executeBatch();

六、事务管理

6.1 JDBC 事务基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
* 转账示例:事务的典型应用场景
* 从 A 账户转出 1000 元到 B 账户
*/
public boolean transfer(Long fromUserId, Long toUserId, BigDecimal amount) {
String deductSql = "UPDATE accounts SET balance = balance - ? WHERE user_id = ? AND balance >= ?";
String addSql = "UPDATE accounts SET balance = balance + ? WHERE user_id = ?";

Connection conn = null;
PreparedStatement deductPs = null;
PreparedStatement addPs = null;

try {
conn = JdbcUtil.getConnection();

// 关闭自动提交,开启手动事务
conn.setAutoCommit(false);

// 第一步:扣款
deductPs = conn.prepareStatement(deductSql);
deductPs.setBigDecimal(1, amount);
deductPs.setLong(2, fromUserId);
deductPs.setBigDecimal(3, amount);
int affectedRows = deductPs.executeUpdate();

// 如果扣款失败(余额不足),回滚事务
if (affectedRows == 0) {
System.out.println("❌ 余额不足,转账失败");
conn.rollback();
return false;
}

// 第二步:充值
addPs = conn.prepareStatement(addSql);
addPs.setBigDecimal(1, amount);
addPs.setLong(2, toUserId);
int addRows = addPs.executeUpdate();

if (addRows == 0) {
System.out.println("❌ 目标账户不存在,转账失败");
conn.rollback();
return false;
}

// 第三步:提交事务
conn.commit();
System.out.println("✅ 转账成功!");
return true;

} catch (SQLException e) {
System.out.println("❌ 转账异常:" + e.getMessage());
// 发生异常,回滚事务
if (conn != null) {
try {
conn.rollback();
System.out.println("🔄 事务已回滚");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return false;
} finally {
// 恢复自动提交状态
if (conn != null) {
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭资源
JdbcUtil.close(null, deductPs, null);
JdbcUtil.close(null, addPs, conn);
}
}

6.2 保存点实现部分回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 带有保存点的事务:可以回滚到指定点
*/
public void transactionWithSavepoint() {
String sql1 = "INSERT INTO orders (user_id, total_amount) VALUES (?, ?)";
String sql2 = "UPDATE users SET status = 0 WHERE id = ?";

Connection conn = null;
PreparedStatement ps1 = null;
PreparedStatement ps2 = null;
Savepoint savepoint = null;

try {
conn = JdbcUtil.getConnection();
conn.setAutoCommit(false);

// 第一步:创建订单
ps1 = conn.prepareStatement(sql1);
ps1.setLong(1, 1L);
ps1.setBigDecimal(2, new BigDecimal("500.00"));
ps1.executeUpdate();

// 设置保存点
savepoint = conn.setSavepoint("after_create_order");
System.out.println("📍 保存点已创建:after_create_order");

// 第二步:更新用户状态
ps2 = conn.prepareStatement(sql2);
ps2.setLong(1, 1L);
ps2.executeUpdate();

// 提交事务
conn.commit();
System.out.println("✅ 事务提交成功");

} catch (SQLException e) {
if (savepoint != null) {
try {
// 回滚到保存点(保留保存点之前的操作)
conn.rollback(savepoint);
System.out.println("🔄 已回滚到保存点:after_create_order");

// 手动提交,保留保存点之前的操作
conn.commit();
System.out.println("✅ 部分操作已提交");
} catch (SQLException ex) {
ex.printStackTrace();
}
} else {
try {
conn.rollback();
System.out.println("🔄 全事务回滚");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
JdbcUtil.close(null, ps1, null);
JdbcUtil.close(null, ps2, conn);
}
}

6.3 事务隔离级别设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 设置事务隔离级别
*/
public void setTransactionIsolationLevel() {
Connection conn = null;

try {
conn = JdbcUtil.getConnection();

// MySQL 支持的隔离级别:
// Connection.TRANSACTION_READ_UNCOMMITTED
// Connection.TRANSACTION_READ_COMMITTED
// Connection.TRANSACTION_REPEATABLE_READ(MySQL 默认)
// Connection.TRANSACTION_SERIALIZABLE

// 设置为可重复读(REPEATABLE READ)
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

System.out.println("当前隔离级别:" + getIsolationLevelName(conn.getTransactionIsolation()));

} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(null, null, conn);
}
}

private String getIsolationLevelName(int level) {
switch (level) {
case Connection.TRANSACTION_READ_UNCOMMITTED:
return "READ_UNCOMMITTED";
case Connection.TRANSACTION_READ_COMMITTED:
return "READ_COMMITTED";
case Connection.TRANSACTION_REPEATABLE_READ:
return "REPEATABLE_READ";
case Connection.TRANSACTION_SERIALIZABLE:
return "SERIALIZABLE";
default:
return "UNKNOWN";
}
}

七、数据库连接池详解

7.1 什么是数据库连接池?

传统 JDBC 每次操作数据库都需要建立新的连接,操作完成后关闭连接。这种方式在高并发场景下性能很差,因为建立 TCP 连接是一个耗时的操作。

连接池的思想是:预先创建一定数量的数据库连接,放置在内存中备用,用完归还而非关闭。

7.2 常见连接池对比

连接池 优点 缺点 推荐场景
Druid(阿里) 功能丰富,监控强大 文档较少 国内项目首选
HikariCP 性能最高,Spring Boot 2.x 默认 功能相对简单 高性能需求
C3P0 发展成熟 性能一般,已停止维护 老项目
DBCP 稳定 配置繁琐 偶有使用

💡 推荐:阿里的 Druid 连接池在国内使用最广,提供了强大的监控和 SQL 防注入功能;Spring Boot 2.x 默认使用 HikariCP,性能最优。

7.3 使用 Druid 连接池

添加依赖( Maven):

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.18</version>
</dependency>

配置文件 druid.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 数据库连接参数
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=your_password

# 连接池配置
initialSize=5 # 初始连接数
maxActive=20 # 最大活跃连接数
maxWait=3000 # 获取连接最大等待时间(毫秒)
minIdle=5 # 最小空闲连接数
timeBetweenEvictionRunsMillis=60000 # 清理线程运行间隔
minEvictableIdleTimeMillis=300000 # 最小空闲时间

# 测试连接
validationQuery=SELECT 1 # 验证连接的 SQL
testWhileIdle=true # 空闲时测试连接
testOnBorrow=false # 借出时测试(影响性能)
testOnReturn=false # 归还时测试

创建 Druid 连接池工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.example.util;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
* Druid 连接池工具类
*/
public class DruidUtil {

// 数据源(单例)
private static DruidDataSource dataSource;

// 线程本地容器:存放每个线程的连接
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

static {
try {
// 加载配置文件
Properties properties = new Properties();
InputStream is = DruidUtil.class.getClassLoader()
.getResourceAsStream("druid.properties");
properties.load(is);

// 创建数据源
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

System.out.println("✅ Druid 连接池初始化成功");

} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("初始化 Druid 连接池失败", e);
}
}

/**
* 获取数据源
*/
public static DataSource getDataSource() {
return dataSource;
}

/**
* 从连接池获取连接
*/
public static Connection getConnection() throws SQLException {
// 先从 ThreadLocal 获取
Connection conn = threadLocal.get();

if (conn == null) {
// 从连接池获取
conn = dataSource.getConnection();
// 存入 ThreadLocal
threadLocal.set(conn);
}

return conn;
}

/**
* 开启事务
*/
public static void beginTransaction() throws SQLException {
Connection conn = getConnection();
conn.setAutoCommit(false);
}

/**
* 提交事务
*/
public static void commit() throws SQLException {
Connection conn = threadLocal.get();
if (conn != null) {
conn.commit();
conn.setAutoCommit(true);
}
}

/**
* 回滚事务
*/
public static void rollback() throws SQLException {
Connection conn = threadLocal.get();
if (conn != null) {
conn.rollback();
conn.setAutoCommit(true);
}
}

/**
* 关闭连接(归还到连接池)
*/
public static void close(Connection conn) {
if (conn != null) {
try {
conn.close(); // 归还到连接池,而非真正关闭
} catch (SQLException e) {
e.printStackTrace();
}
}
}

/**
* 统一关闭资源
*/
public static void close(AutoCloseable... resources) {
for (AutoCloseable resource : resources) {
if (resource != null) {
try {
resource.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

/**
* 获取连接数信息
*/
public static void printPoolStatus() {
System.out.println("活跃连接数:" + dataSource.getActiveCount());
System.out.println("空闲连接数:" + dataSource.getPool().getIdleCount());
System.out.println("等待获取连接的线程数:" + dataSource.getWaitThreadCount());
}
}

使用 Druid 连接池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 方式一:直接使用
Connection conn = DruidUtil.getConnection();
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setLong(1, 1L);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
System.out.println("用户名:" + rs.getString("username"));
}
DruidUtil.close(rs, ps, conn);

// 方式二:开启事务后使用
try {
DruidUtil.beginTransaction();

// 执行多个操作,共用同一个连接
String sql1 = "INSERT INTO orders (user_id, total_amount) VALUES (?, ?)";
PreparedStatement ps1 = DruidUtil.getConnection().prepareStatement(sql1);
ps1.setLong(1, 1L);
ps1.setBigDecimal(2, new BigDecimal("100.00"));
ps1.executeUpdate();

String sql2 = "UPDATE users SET status = 0 WHERE id = ?";
PreparedStatement ps2 = DruidUtil.getConnection().prepareStatement(sql2);
ps2.setLong(1, 1L);
ps2.executeUpdate();

DruidUtil.commit();
System.out.println("✅ 事务提交成功");

} catch (SQLException e) {
DruidUtil.rollback();
e.printStackTrace();
}

7.4 使用 HikariCP 连接池

添加依赖:

1
2
3
4
5
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>

配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.config;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class HikariConfig {

@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();

config.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=Asia/Shanghai");
config.setUsername("root");
config.setPassword("your_password");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");

// 连接池配置
config.setMaximumPoolSize(10); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接
config.setIdleTimeout(600000); // 空闲超时
config.setConnectionTimeout(30000); // 连接超时
config.setMaxLifetime(1800000); // 最大生命周期

return new HikariDataSource(config);
}
}

八、封装 BaseDao 通用工具类

8.1 BaseDao 设计思路

为了减少重复代码,我们可以将 JDBC 的通用操作抽取到 BaseDao 中:

8.2 BaseDao 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package com.example.dao;

import com.example.util.DruidUtil;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
* 泛型 BaseDao:封装通用的增删改查操作
* 子类只需实现 extractEntity() 方法即可拥有完整的 CRUD 功能
*/
public abstract class BaseDao<T> {

/**
* 根据 ID 查询
*/
public T findById(Long id) {
String sql = "SELECT * FROM " + getTableName() + " WHERE id = ?";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setLong(1, id);

rs = ps.executeQuery();

if (rs.next()) {
return extractEntity(rs);
}

} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtil.close(rs, ps, conn);
}

return null;
}

/**
* 查询所有
*/
public List<T> findAll() {
String sql = "SELECT * FROM " + getTableName() + " ORDER BY id DESC";

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement(sql);

rs = ps.executeQuery();

List<T> list = new ArrayList<>();
while (rs.next()) {
list.add(extractEntity(rs));
}

return list;

} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtil.close(rs, ps, conn);
}

return new ArrayList<>();
}

/**
* 通用查询单个(带参数)
*/
public T findOne(String sql, Object... params) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement(sql);

// 设置参数
setParameters(ps, params);

rs = ps.executeQuery();

if (rs.next()) {
return extractEntity(rs);
}

} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtil.close(rs, ps, conn);
}

return null;
}

/**
* 通用查询列表(带参数)
*/
public List<T> findList(String sql, Object... params) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement(sql);

setParameters(ps, params);

rs = ps.executeQuery();

List<T> list = new ArrayList<>();
while (rs.next()) {
list.add(extractEntity(rs));
}

return list;

} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtil.close(rs, ps, conn);
}

return new ArrayList<>();
}

/**
* 插入数据
*/
public int insert(T entity) {
String sql = buildInsertSql();

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

// 设置插入参数(由子类实现)
setInsertParameters(ps, entity);

int rows = ps.executeUpdate();

// 获取自增主键
if (rows > 0) {
rs = ps.getGeneratedKeys();
if (rs.next()) {
setId(entity, rs.getLong(1));
}
}

return rows;

} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtil.close(rs, ps, conn);
}

return 0;
}

/**
* 更新数据
*/
public int update(T entity) {
String sql = buildUpdateSql();

Connection conn = null;
PreparedStatement ps = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement(sql);

// 设置更新参数
setUpdateParameters(ps, entity);

return ps.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtil.close(null, ps, conn);
}

return 0;
}

/**
* 根据 ID 删除
*/
public int deleteById(Long id) {
String sql = "DELETE FROM " + getTableName() + " WHERE id = ?";

Connection conn = null;
PreparedStatement ps = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setLong(1, id);

return ps.executeUpdate();

} catch (SQLException e) {
e.printStackTrace();
} finally {
DruidUtil.close(null, ps, conn);
}

return 0;
}

/**
* 设置预编译 SQL 参数
*/
private void setParameters(PreparedStatement ps, Object... params) throws SQLException {
if (params == null || params.length == 0) {
return;
}

for (int i = 0; i < params.length; i++) {
Object param = params[i];

if (param == null) {
ps.setNull(i + 1, Types.NULL);
} else if (param instanceof Integer) {
ps.setInt(i + 1, (Integer) param);
} else if (param instanceof Long) {
ps.setLong(i + 1, (Long) param);
} else if (param instanceof String) {
ps.setString(i + 1, (String) param);
} else if (param instanceof java.util.Date) {
ps.setTimestamp(i + 1, new Timestamp(((java.util.Date) param).getTime()));
} else if (param instanceof java.sql.Timestamp) {
ps.setTimestamp(i + 1, (java.sql.Timestamp) param);
} else if (param instanceof BigDecimal) {
ps.setBigDecimal(i + 1, (BigDecimal) param);
} else if (param instanceof Double) {
ps.setDouble(i + 1, (Double) param);
} else if (param instanceof Boolean) {
ps.setBoolean(i + 1, (Boolean) param);
} else {
ps.setObject(i + 1, param);
}
}
}

// ==================== 抽象方法:子类实现 ====================

/**
* 获取表名
*/
protected abstract String getTableName();

/**
* 从 ResultSet 提取实体对象
*/
protected abstract T extractEntity(ResultSet rs) throws SQLException;

/**
* 设置插入 SQL 的参数
*/
protected abstract void setInsertParameters(PreparedStatement ps, T entity) throws SQLException;

/**
* 设置更新 SQL 的参数
*/
protected abstract void setUpdateParameters(PreparedStatement ps, T entity) throws SQLException;

/**
* 设置自增主键到实体对象
*/
protected abstract void setId(T entity, long id);

/**
* 构建插入 SQL
*/
protected abstract String buildInsertSql();

/**
* 构建更新 SQL
*/
protected abstract String buildUpdateSql();
}

8.3 UserDao 继承 BaseDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package com.example.dao;

import com.example.entity.User;
import com.example.util.DruidUtil;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
* 用户 DAO:继承 BaseDao,只需实现抽象方法
*/
public class UserDao extends BaseDao<User> {

@Override
protected String getTableName() {
return "users";
}

@Override
protected User extractEntity(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setStatus(rs.getInt("status"));
user.setCreatedAt(rs.getTimestamp("created_at"));
user.setUpdatedAt(rs.getTimestamp("updated_at"));
return user;
}

@Override
protected void setInsertParameters(PreparedStatement ps, User user) throws SQLException {
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getEmail());
ps.setInt(4, user.getStatus() != null ? user.getStatus() : 1);
}

@Override
protected void setUpdateParameters(PreparedStatement ps, User user) throws SQLException {
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getEmail());
ps.setInt(4, user.getStatus());
ps.setLong(5, user.getId());
}

@Override
protected void setId(User user, long id) {
user.setId(id);
}

@Override
protected String buildInsertSql() {
return "INSERT INTO users (username, password, email, status) VALUES (?, ?, ?, ?)";
}

@Override
protected String buildUpdateSql() {
return "UPDATE users SET username = ?, password = ?, email = ?, status = ? WHERE id = ?";
}

// ==================== 扩展方法:业务相关查询 ====================

/**
* 根据用户名和密码查询(登录)
*/
public User login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = ? AND password = ? AND status = 1";
return findOne(sql, username, password);
}

/**
* 分页查询
*/
public java.util.List<User> findByPage(int pageNum, int pageSize) {
int offset = (pageNum - 1) * pageSize;
String sql = "SELECT * FROM users ORDER BY id ASC LIMIT ?, ?";
return findList(sql, offset, pageSize);
}

/**
* 条件查询:用户名模糊匹配 + 状态筛选
*/
public java.util.List<User> search(String keyword, Integer status) {
StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE 1=1");

if (keyword != null && !keyword.trim().isEmpty()) {
sql.append(" AND username LIKE ?");
}
if (status != null) {
sql.append(" AND status = ?");
}

sql.append(" ORDER BY created_at DESC");

if (keyword != null && !keyword.trim().isEmpty() && status != null) {
return findList(sql.toString(), "%" + keyword + "%", status);
} else if (keyword != null && !keyword.trim().isEmpty()) {
return findList(sql.toString(), "%" + keyword + "%");
} else if (status != null) {
return findList(sql.toString(), status);
} else {
return findList(sql.toString());
}
}
}

九、异常处理与最佳实践

9.1 JDBC 异常处理原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* JDBC 异常处理最佳实践
*/
public class JdbcExceptionHandling {

public void properExceptionHandling() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
conn = DruidUtil.getConnection();
ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setLong(1, 1L);
rs = ps.executeQuery();

if (rs.next()) {
System.out.println("找到用户:" + rs.getString("username"));
}

} catch (SQLException e) {
// 分类处理不同类型的 SQL 异常
System.out.println("❌ SQL 异常:" + e.getMessage());

// 常见异常类型判断
if (e instanceof java.sql.SQLIntegrityConstraintViolationException) {
System.out.println("数据完整性冲突:如重复的主键或唯一约束");
} else if (e instanceof java.sql.SQLSyntaxErrorException) {
System.out.println("SQL 语法错误");
} else if (e instanceof java.sql.SQLTimeoutException) {
System.out.println("SQL 执行超时");
} else if (e instanceof java.sql.SQLDataException) {
System.out.println("数据异常:如数据类型不匹配");
} else if (e instanceof com.mysql.cj.jdbc.exceptions.CommunicationsException) {
System.out.println("数据库连接通信异常");
}

} finally {
// 必须在 finally 中关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

9.2 资源关闭的正确方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 使用 try-with-resources 自动关闭资源(推荐)
*/
public void tryWithResourcesExample() {
// Java 7+ 支持 try-with-resources,资源会自动关闭
String sql = "SELECT * FROM users WHERE id = ?";

try (
Connection conn = DruidUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
) {
ps.setLong(1, 1L);

while (rs.next()) {
System.out.println("用户名:" + rs.getString("username"));
}

} catch (SQLException e) {
e.printStackTrace();
}
// 无需手动关闭,资源自动关闭
}

/**
* 封装统一的关闭方法
*/
public static void close(AutoCloseable... resources) {
for (AutoCloseable resource : resources) {
if (resource != null) {
try {
resource.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

9.3 JDBC 最佳实践清单


十、总结

10.1 核心知识点回顾

10.2 学习路线建议

10.3 下一步推荐学习

  • 📖 MyBatis:更强大的持久层框架,简化 JDBC 操作
  • 📖 Hibernate:全自动 ORM 框架
  • 📖 Spring JdbcTemplate:Spring 提供的 JDBC 封装
  • 📖 《高性能 MySQL》:深入理解数据库原理

💡 写给读者的话:JDBC 是 Java 数据库操作的根基,虽然现在有很多 ORM 框架简化了数据库操作,但理解 JDBC 的原理能让你在遇到问题时快速定位根源。”知其然,更知其所以然”,共勉!


📅 本文首次发布于 2026 年 5 月 24 日