纯 JDBC + 反射封装的 BaseDao 以泛型约束实体、反射自动映射字段与列、PreparedStatement 防注入为核心,统一管理连接与资源,强调映射准确、参数匹配、事务清晰、资源干净。

在纯 JDBC + 反射环境下封装通用 BaseDao,核心是用泛型约束实体类型、用反射自动映射字段与列、用 PreparedStatement 防 SQL 注入,并统一管理连接和资源。关键不在“写得多”,而在“映射准、参数对、事务清、资源净”。
一、定义泛型 BaseDao 接口(契约先行)
接口只声明能力,不涉及实现细节,方便后续 Mock 或多数据源扩展:
public interface BaseDao<T> {
// 插入返回主键(支持自增)
Long insert(T entity);
// 根据主键查询
T findById(Long id, Class<T> clazz);
// 查询全部(按类名推表名)
List<T> findAll(Class<T> clazz);
// 根据条件更新(非 null 字段)
int update(T entity);
// 根据主键删除
int deleteById(Long id, Class<T> clazz);
}二、实现类中用反射解析实体元信息
每个实体类对应一张表,字段名默认映射列名(支持 @Column 注解可选增强)。重点处理三件事:
-
表名推导:默认取类简单名小写(
User.class → "user"),也可加@Table(name="sys_user") -
主键识别:找标注
@Id的字段(或约定名为id的 Long 类型字段) -
字段-列映射:遍历所有 getter 方法,排除
getClass/hashCode等,用方法名转下划线(getUserName → user_name),再通过反射读值
示例片段(获取非空字段用于 UPDATE):
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
private <T> Map<String, Object> getNonNullFieldValues(T entity) {
Map<String, Object> params = new LinkedHashMap<>();
for (Method method : entity.getClass().getDeclaredMethods()) {
if (method.getName().startsWith("get") && !method.getName().equals("getClass")) {
String fieldName = Introspector.decapitalize(
method.getName().substring(3)
);
String columnName = camelToUnderline(fieldName); // userName → user_name
try {
Object value = method.invoke(entity);
if (value != null || isUpdateNullableField(fieldName)) {
params.put(columnName, value);
}
} catch (Exception ignored) {}
}
}
return params;
}三、SQL 拼装与 PreparedStatement 绑定一体化
避免字符串拼接 SQL,所有参数走 ? 占位符。利用反射拿到的字段顺序,严格匹配 setObject(i++, value):
-
INSERT:生成
INSERT INTO user (name, age) VALUES (?, ?),按 getter 返回顺序设参 -
UPDATE:动态拼
SET name=?, age=? WHERE id=?,WHERE 固定用主键字段 -
SELECT:查全部时用
SELECT * FROM user,构造对象时按 ResultSetMetaData 列名反查 setter 方法
注意:主键插入后需调用 ps.getGeneratedKeys() 获取自增 ID。
四、连接管理与资源释放必须显式可靠
不依赖 Spring,就自己管好 Connection/PreparedStatement/ResultSet:
- 每次操作从 DataSource 获取新连接(不复用,避免事务污染)
- 使用 try-with-resources 或 finally 块确保三层资源关闭(ResultSet → PreparedStatement → Connection)
- 统一异常转为 RuntimeException(如 DaoException),避免上层强制捕获 SQLException
推荐工具方法:
private void close(AutoCloseable... resources) {
for (AutoCloseable r : resources) {
if (r != null) {
try { r.close(); } catch (Exception ignored) {}
}
}
}不复杂但容易忽略的是字段类型与 JDBC 类型的隐式转换(比如 LocalDateTime 要注册 Timestamp 处理器),以及空值在 INSERT/UPDATE 中是否允许写入 NULL —— 这些需在反射读值时结合字段注解(如 @NotNull)或数据库元数据校验。做好这几点,一个轻量、可测、不绑定框架的 BaseDao 就立住了。

















