第 11 章 · JDBC 与 MySQL 入门
本章目标:理解 JDBC 标准访问数据库的流程;使用 MySQL 驱动建立连接;掌握 PreparedStatement 防 SQL 注入;完成 CRUD 增删改查;了解 连接池概念与为何生产必须用池化。
学时建议:5~6 小时(含 2 小时跟练)
前置:ch07 IO 与异常;ch08 Map 存查询结果。
11.1 JDBC 是什么
JDBC(Java Database Connectivity)是 Java 访问关系型数据库的统一 API:
Java 应用
→ JDBC API(Connection, Statement, ResultSet)
→ 数据库驱动(如 mysql-connector-j)
→ MySQL Server
| 组件 | 作用 |
|---|---|
DriverManager | 获取连接(入门) |
Connection | 数据库会话 |
Statement / PreparedStatement | 执行 SQL |
ResultSet | 查询结果集 |
本教程使用虚构教学库 db.example.com,本地可用 Docker 或本机 MySQL 模拟。
11.2 环境准备
Maven 依赖(pom.xml):
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
连接 URL 格式:
jdbc:mysql://db.example.com:3306/toolkit_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
| 参数 | 说明 |
|---|---|
host | db.example.com 或 127.0.0.1 |
port | 默认 3306 |
database | 库名,如 toolkit_db |
serverTimezone | 避免时区警告 |
useSSL | 教学环境关闭以简化;生产环境必须开启 SSL 或走内网隔离 |
安全提示:useSSL=false仅限本机教学库。连接生产或云上数据库时去掉该参数(MySQL 8 默认useSSL=true),并配合环境变量/密钥管理读取凭据。
切勿在代码中硬编码真实生产密码;练习从环境变量读取:
String url = System.getenv("DB_URL");
String user = System.getenv("DB_USER");
String pass = System.getenv("DB_PASS");
11.3 第一次连接
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JdbcConnectDemo {
public static void main(String[] args) {
String url = "jdbc:mysql://127.0.0.1:3306/toolkit_db"
+ "?useSSL=false&serverTimezone=Asia/Shanghai";
String user = "learn_user";
String password = "learn_pass";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("连接成功: " + conn.getMetaData().getDatabaseProductName());
} catch (SQLException e) {
System.err.println("连接失败: " + e.getMessage());
}
}
}
try-with-resources:Connection 实现 AutoCloseable,块结束自动关闭。
11.4 建表与样本数据
在 MySQL 客户端执行(教学库):
CREATE DATABASE IF NOT EXISTS toolkit_db
DEFAULT CHARACTER SET utf8mb4;
USE toolkit_db;
CREATE TABLE IF NOT EXISTS products (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sku VARCHAR(32) NOT NULL UNIQUE,
title VARCHAR(128) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO products (sku, title, price, stock) VALUES
('BK-001', 'Java 基础', 59.90, 120),
('BK-002', 'Web 开发', 79.00, 45),
('BK-003', '数据分析', 45.00, 89);
11.5 PreparedStatement 查询
永远优先 PreparedStatement,参数绑定防 SQL 注入:
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class ProductQuery {
record Product(long id, String sku, String title, double price, int stock) {}
public List<Product> findByMinPrice(Connection conn, double minPrice) throws SQLException {
String sql = "SELECT id, sku, title, price, stock FROM products WHERE price >= ?";
List<Product> list = new ArrayList<>();
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setDouble(1, minPrice);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
list.add(new Product(
rs.getLong("id"),
rs.getString("sku"),
rs.getString("title"),
rs.getDouble("price"),
rs.getInt("stock")
));
}
}
}
return list;
}
}
| ResultSet 方法 | 类型 |
|---|---|
getLong("id") | BIGINT |
getString("sku") | VARCHAR |
getDouble("price") | DECIMAL |
getTimestamp("created_at") | TIMESTAMP |
11.6 INSERT 增删改
public int insertProduct(Connection conn, String sku, String title,
double price, int stock) throws SQLException {
String sql = "INSERT INTO products (sku, title, price, stock) VALUES (?, ?, ?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, sku);
ps.setString(2, title);
ps.setDouble(3, price);
ps.setInt(4, stock);
return ps.executeUpdate(); // 影响行数
}
}
public int updateStock(Connection conn, String sku, int newStock) throws SQLException {
String sql = "UPDATE products SET stock = ? WHERE sku = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, newStock);
ps.setString(2, sku);
return ps.executeUpdate();
}
}
public int deleteBySku(Connection conn, String sku) throws SQLException {
String sql = "DELETE FROM products WHERE sku = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, sku);
return ps.executeUpdate();
}
}
| 方法 | 用途 |
|---|---|
executeQuery() | SELECT,返回 ResultSet |
executeUpdate() | INSERT/UPDATE/DELETE,返回行数 |
execute() | 任意,需判断类型 |
11.7 获取自增主键
String sql = "INSERT INTO products (sku, title, price, stock) VALUES (?, ?, ?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "BK-004");
ps.setString(2, "算法入门");
ps.setDouble(3, 68.0);
ps.setInt(4, 30);
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
if (keys.next()) {
long newId = keys.getLong(1);
System.out.println("新 ID: " + newId);
}
}
}
11.8 事务入门
多条 SQL 需原子性时用事务:
conn.setAutoCommit(false);
try {
updateStock(conn, "BK-001", 100);