源本科技 | 码上会

Java JDBC 连接 MySQL

2026/03/17
43
0

学习目标

  • 理解 JDBC 的基本工作原理及其在 Java 应用中的作用

  • 掌握加载驱动、建立连接、执行 SQL 及释放资源的标准流程

  • 学会编写通用的数据库连接工具类(DBUtil),实现代码复用

  • 了解防止 SQL 注入的 PreparedStatement 用法及资源管理的最佳实践

  • 熟悉国内开发环境中 MySQL 8.0+ 的驱动配置与时区处理方案


JDBC

Java 数据库连接(JDBC,Java Database Connectivity)是 Java 语言中用来规范客户端程序如何访问数据库的应用程序接口 (API)。它提供了诸如查询和更新数据库中数据的方法。JDBC 是面向关系型数据库的。在实际的企业级开发中,直接使用 JDBC 原生 API 进行数据库操作虽然灵活,但代码冗余度高,尤其是连接的创建与关闭逻辑重复出现。因此,将连接逻辑封装成工具类是行业标准做法。

核心组件

  • DriverManager:用于加载驱动并获取数据库连接

  • Connection:代表与特定数据库的连接会话

  • Statement / PreparedStatement:用于向数据库发送 SQL 语句

  • ResultSet:保存执行查询后返回的结果集


环境准备

在开始编写代码之前,需要确保项目中引入了 MySQL 的官方驱动程序。对于 MySQL 8.0 及以上版本,推荐使用 mysql-connector-j

Maven 依赖配置

在项目的 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 的操作遵循严格的“五步法”。

1. 加载驱动

虽然现代 JDBC 驱动通常能自动注册,但显式加载仍是一种良好的习惯,特别是在复杂类加载环境中。

Class.forName("com.mysql.cj.jdbc.Driver");

2. 获取连接

通过 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);

3. 创建执行器

根据需求选择 StatementPreparedStatement。为了安全起见,强烈建议使用后者。

String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1001);

4. 执行 SQL 并处理结果

执行查询或更新操作,并遍历结果集。

ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
    String name = rs.getString("username");
    System.out.println("用户名称:" + name);
}

5. 释放资源

遵循“后开先闭”原则,依次关闭 ResultSetStatementConnection

rs.close();
pstmt.close();
conn.close();

数据库连接工具类

为了避免在每个业务方法中重复编写连接和关闭代码,我们需要创建一个工具类。该类通常包含获取连接、执行增删改、执行查询以及释放资源的方法。

设计思路

  1. 配置文件分离:将数据库 URL、用户名、密码提取到 db.properties 文件中,避免硬编码。

  2. 静态代码块加载驱动:确保类加载时只初始化一次驱动。

  3. ThreadLocal 管理连接:在事务控制场景下,保证同一个线程获取的是同一个连接(本示例展示基础版,暂不涉及复杂事务管理)。

  4. 统一资源关闭:提供通用的 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_123

DBUtil 工具类实现

import 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 不仅减少了代码重复,还集中管理了异常处理和资源释放逻辑,提高了系统的稳定性。


附录

JDBC 工作流程示意图

测试环境搭建脚本

1. 创建数据库

首先创建一个名为 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;

2. 创建用户表

根据课件中的 Java 代码逻辑,我们需要一张包含 idusernameemail 字段的表。

-- 创建用户表
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='用户信息表';

3. 插入测试数据

为了验证查询、更新等功能,我们预置了 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;