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 采用的是分层架构 ,它位于应用程序和数据库之间,充当桥梁的角色:
flowchart TD
A["☕️ Java 应用层"] --> B["📦 JDBC API"]
B --> C["📋 JDBC 驱动管理器"]
C --> D["🚗 数据库驱动层\n(每种数据库独有)"]
D --> E["🗄️ MySQL\nOracle\nSQL Server\nPostgreSQL ..."]
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#c8e6c9
style D fill:#fff3e0
style E fill:#f8bbd0
各层职责:
层级
组件
职责
应用层
我们的 Java 代码
编写业务逻辑,调用 JDBC API
API 层
java.sql.* 包
提供统一接口,如 DriverManager、Connection、Statement 等
驱动管理层
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 环境准备 在开始之前,你需要准备好以下环境:
**JDK 1.8+**:确保 Java 开发环境已配置
MySQL 数据库 :本地或远程 MySQL 服务
MySQL JDBC 驱动 :mysql-connector-java-8.0.x.jar
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 try { Class.forName("com.mysql.cj.jdbc.Driver" ); } catch (ClassNotFoundException e) { e.printStackTrace(); }
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 { 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 的核心操作离不开四个关键对象,它们之间的关系如下:
flowchart LR
A["📋 DriverManager"] --> B["🔗 Connection"]
B --> C["📝 Statement"]
B --> C2["📝 PreparedStatement"]
C --> D["📦 ResultSet"]
C2 --> D
B --> E["🔒 DatabaseMetaData"]
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#fff3e0
style C2 fill:#fff3e0
style D fill:#f8bbd0
对象
类型
作用
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 Connection conn = DriverManager.getConnection(url, username, password);Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/jdbc_demo?user=root&password=your_password" ); DriverManager.setLoginTimeout(10 ); 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 Statement stmt = connection.createStatement();PreparedStatement ps = connection.prepareStatement(sql);connection.setAutoCommit(false ); connection.setAutoCommit(true ); connection.commit(); connection.rollback(); Savepoint savepoint = connection.setSavepoint("sp1" );connection.rollback(savepoint); DatabaseMetaData metaData = connection.getMetaData();String dbName = metaData.getDatabaseProductName();String dbVersion = metaData.getDatabaseProductVersion();System.out.println("是否有效:" + connection.isValid(5 )); connection.setReadOnly(true ); connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); 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 String sql1 = "SELECT * FROM users WHERE id = 1" ;ResultSet rs = statement.executeQuery(sql1);String sql2 = "INSERT INTO users (username, password, email) VALUES ('test', '123456', 'test@example.com')" ;int rows = statement.executeUpdate(sql2);System.out.println("插入了 " + rows + " 行数据" ); String sql3 = "SELECT * FROM users" ;boolean hasResult = statement.execute(sql3);System.out.println("是否有结果集:" + hasResult); 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(); statement.clearBatch(); 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); } 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 while (rs.next()) { Long id = rs.getLong("id" ); String username = rs.getString("username" ); String email = rs.getString("email" ); Long id2 = rs.getLong(1 ); String username2 = rs.getString(2 ); System.out.println(id + " - " + username + " - " + email); } rs.getInt("id" ); rs.getLong("id" ); rs.getDouble("balance" ); rs.getBigDecimal("amt" ); rs.getBoolean("status" ); rs.getDate("created_at" ); rs.getTime("login_time" ); rs.getTimestamp("updated_at" ); rs.getString("content" ); rs.getBytes("file_data" ); rs.getBlob("avatar" ); rs.getClob("description" ); if (rs.next()) { } else { } rs.beforeFirst(); rs.afterLast(); rs.first(); rs.last(); rs.absolute(5 ); rs.relative(2 ); ResultSetMetaData metaData = rs.getMetaData();System.out.println("总列数:" + metaData.getColumnCount()); System.out.println("第一列名称:" + metaData.getColumnName(1 )); System.out.println("第一列类型:" + metaData.getColumnTypeName(1 )); Statement stmt = connection.createStatement( ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY ); Statement stmtScroll = connection.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE ); rs.absolute(1 ); rs.updateString("email" , "new_email@example.com" ); rs.updateRow(); rs.moveToInsertRow(); rs.updateString("username" , "new_user" ); rs.updateString("password" , "new_pass" ); rs.updateString("email" , "new_user@example.com" ); rs.insertRow();
四、CRUD 实战:增删改查 4.1 项目结构设计
flowchart TD
A["📁 jdbc-project"] --> B["📁 src/main/java"]
B --> C["📁 com.example.dao"]
B --> D["📁 com.example.entity"]
B --> E["📁 com.example.util"]
B --> F["📁 com.example.test"]
A --> G["📁 src/main/resources"]
G --> H["📄 jdbc.properties"]
C --> C1["UserDao.java"]
D --> D1["User.java"]
E --> E1["JdbcUtil.java"]
F --> F1["UserDaoTest.java"]
style A fill:#e3f2fd
style C fill:#c8e6c9
style D fill:#fff3e0
style E fill:#f8bbd0
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 ; } 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.*;public class UserDao { public User findById (Long id) { String sql = "SELECT * FROM users WHERE id = ?" ; Connection conn = null ; PreparedStatement ps = null ; ResultSet rs = null ; try { conn = JdbcUtil.getConnection(); 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 ; } 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 ; } 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 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 应用中最常见的安全漏洞之一。让我们先看看它是如何发生的:
flowchart LR
A["👤 用户输入"] --> B["⚠️ 恶意输入"]
B --> C["📝 拼接 SQL"]
C --> D["💥 SQL 注入攻击"]
A --> A1["正常输入"]
A1 --> B1["参数化查询"]
B1 --> D1["✅ 安全执行"]
style A fill:#e3f2fd
style B fill:#ffcdd2
style C fill:#fff3e0
style D fill:#f8bbd0
style B1 fill:#c8e6c9
style D1 fill:#c8e6c9
5.2 SQL 注入示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public User findByUsernameDangerous (String username) { String sql = "SELECT * FROM users WHERE username = '" + username + "'" ; }
5.3 预编译 SQL 防注入 1 2 3 4 5 6 7 8 9 10 public User findByUsernameSafe (String username) { String sql = "SELECT * FROM users WHERE username = ?" ; }
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);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" )); ps.setTimestamp(6 , new Timestamp (System.currentTimeMillis())); ps.setNull(4 , Types.INTEGER); 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 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(); 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 连接是一个耗时的操作。
连接池 的思想是:预先创建一定数量的数据库连接,放置在内存中备用,用完归还而非关闭。
flowchart LR
A["❌ 传统 JDBC"] --> A1["每次请求\n建立新连接"]
A1 --> A2["耗时\n2-3秒"]
B["✅ 连接池"] --> B1["预先创建\n连接队列"]
B1 --> B2["快速获取\n0.1秒"]
style A fill:#ffcdd2
style A1 fill:#ffcdd2
style A2 fill:#ffcdd2
style B fill:#c8e6c9
style B1 fill:#c8e6c9
style B2 fill:#c8e6c9
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;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 { Connection conn = threadLocal.get(); if (conn == null ) { conn = dataSource.getConnection(); 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 中:
flowchart TD
A["🧱 BaseDao"] --> B["T findById\n(Long id)"]
A --> C["List findAll\n()"]
A --> D["int insert\n(T entity)"]
A --> E["int update\n(T entity)"]
A --> F["int delete\n(Long id)"]
A --> G["T findOne\n(String sql, Object... params)"]
A --> H["List findList\n(String sql, Object... params)"]
B --> I["👤 UserDao"]
C --> I
D --> I
E --> I
F --> I
style A fill:#e3f2fd
style I fill:#c8e6c9
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;public abstract class BaseDao <T> { 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 ; } 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 ; } 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 () ; protected abstract T extractEntity (ResultSet rs) throws SQLException; protected abstract void setInsertParameters (PreparedStatement ps, T entity) throws SQLException; protected abstract void setUpdateParameters (PreparedStatement ps, T entity) throws SQLException; protected abstract void setId (T entity, long id) ; protected abstract String buildInsertSql () ; 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;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 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) { 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 { 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 public void tryWithResourcesExample () { 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 最佳实践清单
flowchart TD
A["✅ JDBC 最佳实践"] --> B["连接管理"]
A --> C["SQL 编写"]
A --> D["事务处理"]
A --> E["性能优化"]
A --> F["安全防护"]
B --> B1["使用连接池\n不用传统 JDBC"]
B --> B2["使用 ThreadLocal\n保证线程安全"]
B --> B3["正确关闭资源\nfinally 中关闭"]
C --> C1["使用 PreparedStatement\n防止 SQL 注入"]
C --> C2["避免 SELECT *\n明确指定列名"]
C --> C3["合理使用 LIMIT\n避免全表扫描"]
D --> D1["事务不宜过长\n减少锁竞争"]
D --> D2["手动提交\nsetAutoCommit(false)"]
D --> D3["异常必须回滚\nrollback()"]
E --> E1["批量操作\naddBatch()"]
E --> E2["使用索引\nEXPLAIN 分析"]
E --> E3["连接池调优\n合理配置参数"]
F --> F1["预编译 SQL\n参数绑定"]
F --> F2["密码加密\nBCrypt 等"]
F --> F3["输入校验\n白名单过滤"]
style A fill:#e3f2fd
十、总结 10.1 核心知识点回顾
mindmap
root((JDBC 核心知识))
基础概念
JDBC 架构
驱动类型
URL 连接字符串
核心 API
DriverManager
Connection
Statement
PreparedStatement
ResultSet
CRUD 操作
查询 executeQuery
插入 executeUpdate
更新 executeUpdate
删除 executeUpdate
事务管理
自动提交
手动提交 commit
回滚 rollback
保存点
SQL 注入防护
PreparedStatement
参数绑定
预编译机制
连接池
Druid
HikariCP
原理:复用连接
性能优化
批量操作
分页查询
索引优化
异常处理
SQLException
资源关闭
事务回滚
10.2 学习路线建议
flowchart LR
A["第一阶段\n基础入门"] --> B["第二阶段\n核心API"]
B --> C["第三阶段\n高级特性"]
C --> D["第四阶段\n实战封装"]
A --> A1["JDBC 概念"]
A --> A2["驱动加载"]
A --> A3["简单 CRUD"]
B --> B1["Connection"]
B --> B2["PreparedStatement"]
B --> B3["ResultSet"]
C --> C1["事务管理"]
C --> C2["连接池"]
C --> C3["性能优化"]
D --> D1["BaseDao 封装"]
D --> D2["ORM 框架\n(如 MyBatis)"]
D --> D3["项目实战"]
style A fill:#e3f2fd
style B fill:#c8e6c9
style C fill:#fff3e0
style D fill:#f8bbd0
10.3 下一步推荐学习
📖 MyBatis :更强大的持久层框架,简化 JDBC 操作
📖 Hibernate :全自动 ORM 框架
📖 Spring JdbcTemplate :Spring 提供的 JDBC 封装
📖 《高性能 MySQL》 :深入理解数据库原理
💡 写给读者的话 :JDBC 是 Java 数据库操作的根基,虽然现在有很多 ORM 框架简化了数据库操作,但理解 JDBC 的原理能让你在遇到问题时快速定位根源。”知其然,更知其所以然”,共勉!
📅 本文首次发布于 2026 年 5 月 24 日