在 Java 操作数据库的过程中,原生 JDBC 代码存在大量重复逻辑:加载驱动、获取连接、释放资源...... 这些代码在每个业务中都要写一遍,不仅繁琐,还容易出错。
本文是个人的一些学习笔记,主要内容如下:
- 原生 JDBC 写法与封装后写法对比
- 配置文件 + 工具类封装
- 增删改查 4 个完整测试案例
- 项目结构与代码规范说明
一、项目结构说明
当前的项目结构如下,所有代码均放在 com.qcby 包下,配置文件放在 resources 目录:
src
├── main
│ ├── java
│ │ └── com.qcby
│ │ ├── JDBCUtils.java // 封装好的工具类
│ │ ├── TestJDBC.java // 原生JDBC查询案例(封装前)
│ │ ├── TestJDBCUtils.java // 封装后查询案例
│ │ ├── TestUpdate.java // 封装后增删改案例
│ └── resources
│ └── db.properties // 数据库配置文件
二、封装前:原生 JDBC 写法(以查询为例)
1. 原生代码痛点
- 硬编码数据库连接信息(URL、账号、密码),修改时需要改代码
- 每次都要手动写加载驱动、获取连接、释放资源的重复代码
- 资源关闭操作分散,容易遗漏导致连接泄漏
2. 原生查询案例代码(TestJDBC.java)
java
package com.qcby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) throws Exception {
// 1. 加载驱动(硬编码驱动类路径)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 获取连接(硬编码数据库连接信息)
String url = "jdbc:mysql:///jdbcdemo?serverTimezone=UTC&useSSL=false";
String user = "root";
String password = "Zhen@777";
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 获取执行SQL的对象
Statement stmt = conn.createStatement();
// 4. 执行查询SQL
String sql = "select * from t_user";
ResultSet rs = stmt.executeQuery(sql);
// 5. 遍历结果集
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String pwd = rs.getString("password");
String email = rs.getString("email");
System.out.println(id + "\t" + username + "\t" + pwd + "\t" + email);
}
// 6. 手动释放所有资源(顺序:结果集→Statement→连接)
rs.close();
stmt.close();
conn.close();
}
}
三、封装:配置文件 + JDBC 工具类
1. 数据库配置文件(db.properties)
放在 src/main/resources 目录下,统一管理数据库连接信息,避免硬编码:
properties:
# 驱动类路径(MySQL8.0+用cj包)
driverClass=com.mysql.cj.jdbc.Driver
# 数据库连接URL(必须加时区配置)
url=jdbc:mysql:///jdbcdemo?serverTimezone=UTC&useSSL=false
# 数据库账号
username=root
# 数据库密码(修改为你自己的)
password=Zhen@777
2. JDBC 工具类(JDBCUtils.java)
封装加载驱动、获取连接、释放资源的通用逻辑,一次编写,到处使用:
java
package com.qcby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
/**
* JDBC工具类:封装通用的数据库操作逻辑
* 功能:1. 读取配置文件 2. 加载驱动 3. 获取连接 4. 释放资源
*/
public class JDBCUtils {
// 配置文件对象,静态加载一次
private static Properties props = new Properties();
// 静态代码块:类加载时自动执行,只执行一次
static {
try {
// 读取resources下的db.properties配置文件
props.load(JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties"));
// 加载驱动(从配置文件读取驱动类路径)
Class.forName(props.getProperty("driverClass"));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("JDBC工具类初始化失败,请检查配置文件!", e);
}
}
/**
* 获取数据库连接
* @return Connection 数据库连接对象
*/
public static Connection getConnection() {
try {
return DriverManager.getConnection(
props.getProperty("url"),
props.getProperty("username"),
props.getProperty("password")
);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 释放资源(查询场景:关闭ResultSet、Statement、Connection)
* @param conn 数据库连接对象
* @param stmt Statement对象
* @param rs 结果集对象
*/
public static void close(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 释放资源(增删改场景:关闭Statement、Connection,无需关闭ResultSet)
* @param conn 数据库连接对象
* @param stmt Statement对象
*/
public static void close(Connection conn, Statement stmt) {
close(conn, stmt, null);
}
}
四、封装后:增删改查
1. 查询案例(TestJDBCUtils.java)
使用工具类完成查询,代码大幅简化,只关注业务 SQL:
java
package com.qcby;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class TestJDBCUtils {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. 从工具类获取连接(一行代码搞定)
conn = JDBCUtils.getConnection();
// 2. 获取执行SQL的对象
stmt = conn.createStatement();
// 3. 编写并执行查询SQL
String sql = "select * from t_user";
rs = stmt.executeQuery(sql);
// 4. 遍历结果集并输出
while (rs.next()) {
System.out.println(
rs.getInt("id") + "\t" +
rs.getString("username") + "\t" +
rs.getString("password") + "\t" +
rs.getString("email")
);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5. 释放所有资源(调用工具类方法)
JDBCUtils.close(conn, stmt, rs);
}
}
}
2. 新增案例(TestUpdate.java 中 insert 部分)
java
package com.qcby;
import java.sql.Connection;
import java.sql.Statement;
public class TestUpdate {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1. 获取连接
conn = JDBCUtils.getConnection();
// 2. 获取执行对象
stmt = conn.createStatement();
// 3. 编写新增SQL
String sql = "insert into t_user values(null,'封装测试','666','test@qq.com')";
// 4. 执行增删改SQL(executeUpdate返回影响行数)
int rows = stmt.executeUpdate(sql);
System.out.println("新增数据成功,影响行数:" + rows);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5. 释放资源(增删改场景,无需ResultSet)
JDBCUtils.close(conn, stmt);
}
}
}
3. 修改案例(TestUpdate.java 中 update 部分)
java
package com.qcby;
import java.sql.Connection;
import java.sql.Statement;
public class TestUpdate {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = JDBCUtils.getConnection();
stmt = conn.createStatement();
// 修改SQL:将用户名为"封装测试"的密码改为888888
String sql = "update t_user set password='888888' where username='封装测试'";
int rows = stmt.executeUpdate(sql);
System.out.println("修改数据成功,影响行数:" + rows);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(conn, stmt);
}
}
}
4. 删除案例(TestUpdate.java 中 delete 部分)
java
package com.qcby;
import java.sql.Connection;
import java.sql.Statement;
public class TestUpdate {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = JDBCUtils.getConnection();
stmt = conn.createStatement();
// 删除SQL:删除用户名为"封装测试"的记录
String sql = "delete from t_user where username='封装测试'";
int rows = stmt.executeUpdate(sql);
System.out.println("删除数据成功,影响行数:" + rows);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(conn, stmt);
}
}
}
五、封装前后对比总结
| 对比维度 | 原生 JDBC 写法 | 工具类封装写法 |
|---|---|---|
| 代码量 | 每个业务都要写连接、释放资源,代码冗余 | 仅关注业务 SQL,代码量减少 60% 以上 |
| 可维护性 | 数据库信息硬编码,修改需改动多处代码 | 配置文件统一管理,修改仅需改动配置文件 |
| 错误率 | 手动关闭资源易遗漏,导致连接泄漏 | 工具类统一处理,避免资源泄漏问题 |
| 复用性 | 无复用性,每个业务重复造轮子 | 工具类可在所有业务中直接调用 |
六、运行与验证步骤
- 检查配置文件 :确认
db.properties中的账号、密码、URL 配置正确 - 运行查询案例 :执行
TestJDBCUtils.java,控制台输出t_user表所有数据 - 运行新增案例 :执行
TestUpdate.java中的新增代码,控制台输出影响行数:1,数据库新增一条数据 - 运行修改案例:执行修改代码,数据库中对应记录的密码被更新
- 运行删除案例:执行删除代码,数据库中新增的测试数据被删除
七、后续优化方向(其他博客有介绍)
- 引入
PreparedStatement防止 SQL 注入 - 封装事务管理方法,实现业务操作的原子性
- 增加连接池(如 Druid),提升数据库连接性能