JDBC基础(2)
作者:没有四次元口袋的蓝胖
日期:2026-06-29
标签:Java, JDBC, 工具类
一、为什么要封装工具类?
原生 JDBC 代码有很多重复:
- 每次都要写注册驱动、获取连接
- 每次都要写繁琐的释放资源
- 连接参数硬编码在代码里,修改不方便
封装 JDBCUtils 的目的: 减少重复代码,提高开发效率。
二、JDBCUtils 工具类的编写
2.1 配置文件(jdbc.properties)
把连接参数放到配置文件里,方便修改:
properties
# jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db_name?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username=root
password=123456
2.2 工具类代码
java
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* JDBC 工具类
* 功能:获取连接、释放资源
*/
public class JDBCUtils {
private static String driver;
private static String url;
private static String username;
private static String password;
// 静态代码块:类加载时读取配置文件、注册驱动,只执行一次
static {
try {
// 1. 读取配置文件
Properties prop = new Properties();
// 使用类加载器读取 src 下的配置文件
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
prop.load(is);
// 2. 获取参数
driver = prop.getProperty("driver");
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
// 3. 注册驱动
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("JDBC 配置文件加载失败", e);
}
}
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
/**
* 释放资源(增删改用,无 ResultSet)
*/
public static void close(Connection conn, Statement stmt) {
close(null, stmt, conn);
}
/**
* 释放资源(查询用,有 ResultSet)
*/
public static void close(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(); }
}
}
}
2.3 使用工具类
java
// 查询示例
public Student findById(int id) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
String sql = "SELECT * FROM student WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
rs = pstmt.executeQuery();
Student stu = null;
if (rs.next()) {
stu = new Student();
stu.setId(rs.getInt("id"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
}
return stu;
} catch (SQLException e) {
e.printStackTrace();
return null;
} finally {
JDBCUtils.close(rs, pstmt, conn);
}
}
工具类的好处:
- 连接参数统一管理,修改方便
- 获取连接和释放资源不用重复写
- 代码更简洁
2.4 通用增删改方法
可以进一步封装通用的增删改方法:
java
/**
* 通用增删改方法
* @param sql SQL 语句
* @param args 参数(可变参数,对应 ? 的值)
* @return 影响的行数
*/
public static int update(String sql, Object... args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
// 设置参数
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
return pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
return 0;
} finally {
close(null, pstmt, conn);
}
}
使用:
java
// 增
JDBCUtils.update("INSERT INTO student(name, age) VALUES(?, ?)", "王五", 23);
// 删
JDBCUtils.update("DELETE FROM student WHERE id = ?", 3);
// 改
JDBCUtils.update("UPDATE student SET age = ? WHERE name = ?", 25, "王五");
2.5 通用查询方法(返回集合)
java
/**
* 通用查询方法
* @param sql SQL 语句
* @param handler 结果集处理器(自己定义接口)
* @param args 参数
*/
public static <T> T query(String sql, ResultSetHandler<T> handler, Object... args) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
rs = pstmt.executeQuery();
return handler.handle(rs); // 交给处理器处理结果集
} catch (SQLException e) {
e.printStackTrace();
return null;
} finally {
close(rs, pstmt, conn);
}
}
// 结果集处理器接口
public interface ResultSetHandler<T> {
T handle(ResultSet rs) throws SQLException;
}
// 把结果集封装成 List<Student> 的处理器
public class BeanListHandler<T> implements ResultSetHandler<List<T>> {
private Class<T> clazz;
public BeanListHandler(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public List<T> handle(ResultSet rs) throws SQLException {
List<T> list = new ArrayList<>();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while (rs.next()) {
try {
T obj = clazz.newInstance();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
Object value = rs.getObject(i);
// 反射给字段赋值
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj, value);
}
list.add(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
return list;
}
}
使用:
java
List<Student> list = JDBCUtils.query(
"SELECT * FROM student WHERE age > ?",
new BeanListHandler<>(Student.class),
18
);
三、Apache DbUtils 组件
手写通用查询比较麻烦,Apache 提供了成熟的工具类 commons-dbutils,它已经帮我们封装好了通用的增删改查和结果集处理器。
3.1 引入依赖
Maven 项目在 pom.xml 中添加:
xml
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
普通 Java 项目直接导入 jar 包即可:
commons-dbutils-1.7.jar
3.2 QueryRunner ------ 核心类
QueryRunner 是 DbUtils 的核心操作类,它提供了增删改查的方法。
java
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.*;
// 创建 QueryRunner 对象
QueryRunner qr = new QueryRunner();
3.3 增删改(update)
java
// 增
String insertSql = "INSERT INTO student(name, age) VALUES(?, ?)";
int rows = qr.update(conn, insertSql, "赵六", 24);
// 删
String deleteSql = "DELETE FROM student WHERE id = ?";
int rows2 = qr.update(conn, deleteSql, 1);
// 改
String updateSql = "UPDATE student SET age = ? WHERE name = ?";
int rows3 = qr.update(conn, updateSql, 26, "赵六");
如果传入
DataSource(连接池)给 QueryRunner 构造方法,就不用每次传 Connection 了。
3.4 查询 ------ ResultSetHandler 八大实现类
| 实现类 | 作用 |
|---|---|
BeanHandler<T> |
将结果集第一行封装为一个 JavaBean 对象 |
BeanListHandler<T> |
将结果集所有行封装为 List |
ScalarHandler |
将结果集第一行第一列封装为 Object(用于统计查询 count/sum 等) |
MapHandler |
将结果集第一行封装为 Map(key=列名, value=值) |
MapListHandler |
将结果集所有行封装为 List |
ArrayHandler |
将结果集第一行封装为 Object\[\] |
ArrayListHandler |
将结果集所有行封装为 List<Object\[\]> |
ColumnListHandler |
将结果集某一列封装为 List |
3.5 查询一个对象(BeanHandler)
java
String sql = "SELECT * FROM student WHERE id = ?";
Student stu = qr.query(conn, sql, new BeanHandler<>(Student.class), 1);
System.out.println(stu);
3.6 查询集合(BeanListHandler)
java
String sql = "SELECT * FROM student WHERE age > ?";
List<Student> list = qr.query(conn, sql, new BeanListHandler<>(Student.class), 18);
for (Student s : list) {
System.out.println(s);
}
3.7 统计查询(ScalarHandler)
java
String sql = "SELECT COUNT(*) FROM student";
Long count = qr.query(conn, sql, new ScalarHandler<>());
System.out.println("总记录数:" + count);
注意:
SELECT COUNT(*)返回的是Long类型,不是int,要用Long接收。
3.8 查询 Map 集合
java
// 查询一条 → Map
Map<String, Object> map = qr.query(conn, "SELECT * FROM student WHERE id = ?",
new MapHandler(), 1);
// 查询多条 → List<Map>
List<Map<String, Object>> mapList = qr.query(conn, "SELECT * FROM student",
new MapListHandler());
3.9 自定义 ResultSetHandler
如果内置的处理器满足不了需求,可以自定义:
java
// 自定义:只取姓名列
List<String> names = qr.query(conn, "SELECT name FROM student",
new ResultSetHandler<List<String>>() {
@Override
public List<String> handle(ResultSet rs) throws SQLException {
List<String> list = new ArrayList<>();
while (rs.next()) {
list.add(rs.getString("name"));
}
return list;
}
});
3.10 DbUtils 工具类
DbUtils 还提供了一个 DbUtils 类,用于释放资源,比自己写的更简洁:
java
import org.apache.commons.dbutils.DbUtils;
// 关闭连接
DbUtils.close(conn);
DbUtils.closeQuietly(conn); // 安静关闭,不抛异常
// 关闭多个
DbUtils.closeQuietly(conn, stmt, rs);
四、封装实体类的注意事项
使用 BeanHandler / BeanListHandler 时,JavaBean 必须满足:
- 私有字段:成员变量私有
- 无参构造:必须提供无参构造方法(反射要用到)
- getter/setter:提供所有字段的 getter 和 setter 方法
- 字段名对应:数据库列名要和 JavaBean 属性名一致(或用别名对应)
java
public class Student {
private int id;
private String name;
private int age;
// 必须有无参构造
public Student() {}
// getter 和 setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
列名和属性名不一致怎么办? 用 SQL 别名:
sql
SELECT user_id AS userId, user_name AS userName FROM user
五、思维导图速览
JDBC 进阶
├── JDBCUtils 工具类
│ ├── 配置文件 jdbc.properties
│ ├── 静态代码块加载配置、注册驱动
│ ├── getConnection() 获取连接
│ ├── close() 释放资源(方法重载)
│ ├── 通用 update(增删改)
│ └── 通用 query + ResultSetHandler
├── Apache DbUtils ✅
│ ├── QueryRunner(核心操作类)
│ │ ├── update(增删改)
│ │ └── query(查询)
│ ├── ResultSetHandler 八大实现
│ │ ├── BeanHandler → 一个对象
│ │ ├── BeanListHandler → List集合
│ │ ├── ScalarHandler → 单个值(统计)
│ │ ├── MapHandler / MapListHandler
│ │ ├── ArrayHandler / ArrayListHandler
│ │ └── ColumnListHandler
│ └── DbUtils 类(释放资源)
└── JavaBean 要求
├── 私有字段
├── 无参构造
├── getter/setter
└── 列名与属性名对应
六、写在最后
- JDBCUtils 必须自己手写一遍:理解工具类的封装思想,这是面试常考的"手写 JDBC 工具类"
- DbUtils 要会用:实际开发用它比自己写通用方法方便得多
- BeanHandler / BeanListHandler 是重点:最常用的两个结果集处理器
- ScalarHandler 注意返回类型:count 返回 Long,别用 int 接
常见面试点
- 手写 JDBC 工具类
- PreparedStatement 和 Statement 的区别(SQL 注入)
- ResultSetHandler 的实现类有哪些
- JavaBean 封装的要求