理解 JDBC 的基本工作原理及其在 Java 应用中的作用
掌握加载驱动、建立连接、执行 SQL 及释放资源的标准流程
学会编写通用的数据库连接工具类(DBUtil),实现代码复用
了解防止 SQL 注入的 PreparedStatement 用法及资源管理的最佳实践
熟悉国内开发环境中 MySQL 8.0+ 的驱动配置与时区处理方案
Java 数据库连接(JDBC,Java Database Connectivity)是 Java 语言中用来规范客户端程序如何访问数据库的应用程序接口 (API)。它提供了诸如查询和更新数据库中数据的方法。JDBC 是面向关系型数据库的。在实际的企业级开发中,直接使用 JDBC 原生 API 进行数据库操作虽然灵活,但代码冗余度高,尤其是连接的创建与关闭逻辑重复出现。因此,将连接逻辑封装成工具类是行业标准做法。
核心组件
DriverManager:用于加载驱动并获取数据库连接
Connection:代表与特定数据库的连接会话
Statement / PreparedStatement:用于向数据库发送 SQL 语句
ResultSet:保存执行查询后返回的结果集
在开始编写代码之前,需要确保项目中引入了 MySQL 的官方驱动程序。对于 MySQL 8.0 及以上版本,推荐使用 mysql-connector-j。
在项目的 pom.xml 文件中添加以下依赖:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>在构建连接 URL 时,国内开发者常遇到时区问题和字符编码问题。标准的连接字符串应包含以下关键参数:
useSSL=false:开发环境通常关闭 SSL 以简化配置(生产环境建议开启)
serverTimezone=Asia/Shanghai:指定服务器时区为上海时间,避免时间戳错误
characterEncoding=utf-8:确保中文数据不出现乱码
allowPublicKeyRetrieval=true:MySQL 8.0+ 认证机制所需
在不使用任何框架的情况下,原生 JDBC 的操作遵循严格的“五步法”。
虽然现代 JDBC 驱动通常能自动注册,但显式加载仍是一种良好的习惯,特别是在复杂类加载环境中。
Class.forName("com.mysql.cj.jdbc.Driver");通过 DriverManager 获取 Connection 对象。
String url = "jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8";
String user = "root";
String password = "your_password";
Connection conn = DriverManager.getConnection(url, user, password);根据需求选择 Statement 或 PreparedStatement。为了安全起见,强烈建议使用后者。
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1001);执行查询或更新操作,并遍历结果集。
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String name = rs.getString("username");
System.out.println("用户名称:" + name);
}遵循“后开先闭”原则,依次关闭 ResultSet、Statement 和 Connection。
rs.close();
pstmt.close();
conn.close();为了避免在每个业务方法中重复编写连接和关闭代码,我们需要创建一个工具类。该类通常包含获取连接、执行增删改、执行查询以及释放资源的方法。
配置文件分离:将数据库 URL、用户名、密码提取到 db.properties 文件中,避免硬编码。
静态代码块加载驱动:确保类加载时只初始化一次驱动。
ThreadLocal 管理连接:在事务控制场景下,保证同一个线程获取的是同一个连接(本示例展示基础版,暂不涉及复杂事务管理)。
统一资源关闭:提供通用的 close 方法处理异常。
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8
username=root
password=secure_password_123import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class DBUtil {
private static String driver;
private static String url;
private static String username;
private static String password;
// 静态代码块:类加载时执行一次,读取配置并加载驱动
static {
try {
Properties props = new Properties();
InputStream is = DBUtil.class.getClassLoader().getResourceAsStream("db.properties");
if (is == null) {
throw new RuntimeException("无法找到数据库配置文件 db.properties");
}
props.load(is);
driver = props.getProperty("driver");
url = props.getProperty("url");
username = props.getProperty("username");
password = props.getProperty("password");
Class.forName(driver);
} catch (Exception e) {
throw new RuntimeException("数据库驱动加载失败:" + e.getMessage(), e);
}
}
// 私有构造方法,防止实例化
private DBUtil() {}
/**
* 获取数据库连接
* @return Connection 对象
* @throws SQLException 如果获取连接失败
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
/**
* 执行增删改操作 (INSERT, UPDATE, DELETE)
* @param sql SQL 语句
* @param args SQL 中的参数
* @return 受影响的行数
*/
public static int executeUpdate(String sql, Object... args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
// 填充参数
if (args != null) {
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
}
return pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("执行更新操作失败", e);
} finally {
closeResources(null, pstmt, conn);
}
}
/**
* 通用资源关闭方法
* @param rs ResultSet 对象
* @param stmt Statement 对象
* @param conn Connection 对象
*/
public static void closeResources(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
}
if (stmt != null) {
try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
}
if (conn != null) {
try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}用户查询功能
下面演示如何利用封装好的 DBUtil 完成一个具体的用户查询功能。
根据用户 ID 查询用户信息,并打印到控制台。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserQueryDemo {
public static void main(String[] args) {
int userId = 1001;
queryUserById(userId);
}
public static void queryUserById(int id) {
String sql = "SELECT id, username, email FROM users WHERE id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 1. 获取连接
conn = DBUtil.getConnection();
// 2. 预编译 SQL
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
// 3. 执行查询
rs = pstmt.executeQuery();
// 4. 处理结果
if (rs.next()) {
String name = rs.getString("username");
String email = rs.getString("email");
System.out.println("找到用户 - ID: " + id + ", 姓名:" + name + ", 邮箱:" + email);
} else {
System.out.println("未找到 ID 为 " + id + " 的用户");
}
} catch (SQLException e) {
System.err.println("数据库查询发生异常:" + e.getMessage());
} finally {
// 5. 释放资源
DBUtil.closeResources(rs, pstmt, conn);
}
}
}配置外置:数据库连接信息必须存放在 .properties 配置文件中,严禁硬编码在 Java 源文件里,以便于部署和维护。
防注入:永远优先使用 PreparedStatement 而不是 Statement,前者能有效防止 SQL 注入攻击。
资源闭环:数据库资源(Connection, Statement, ResultSet)是有限的,必须在 finally 块或使用 try-with-resources 语法中确保关闭。
时区陷阱:连接 MySQL 8.0+ 时,务必在 URL 中指定 serverTimezone,否则可能抛出时区相关异常。
工具类价值:封装 DBUtil 不仅减少了代码重复,还集中管理了异常处理和资源释放逻辑,提高了系统的稳定性。

首先创建一个名为 demo_db 的数据库,并设置字符集为 utf8mb4,以完美支持中文及特殊符号。
-- 如果数据库已存在则先删除,避免报错
DROP DATABASE IF EXISTS demo_db;
-- 创建数据库并指定字符集
CREATE DATABASE demo_db
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
-- 切换使用该数据库
USE demo_db;根据课件中的 Java 代码逻辑,我们需要一张包含 id、username 和 email 字段的表。
-- 创建用户表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键 ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名,不可重复',
email VARCHAR(100) DEFAULT NULL COMMENT '电子邮箱',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';为了验证查询、更新等功能,我们预置了 5 条具有代表性的测试数据,包含中文名和不同的邮箱格式。
-- 插入测试数据
INSERT INTO users (username, email) VALUES
('张三', 'zhangsan@example.com'),
('李四', 'lisi@tech.cn'),
('王五', 'wangwu@demo.org'),
('赵六', 'zhaoliu@mail.net'),
('孙七', 'sunqi@company.com');
-- 验证数据是否插入成功
SELECT * FROM users;