可以利用反射和自定义注解(@Table、@Id、@Column)实现实体类与数据库表的自动映射,动态生成SQL并完成通用增删改查操作,避免重复DAO代码。

可以用反射自动完成实体类与数据库表的字段映射,配合 JDBC 实现通用的增删改查操作。核心是通过 Class 获取字段名、类型,结合注解(如 @Table、@Id、@Column)控制 SQL 生成逻辑,避免为每个 DAO 手写重复代码。
定义基础注解,标记实体元信息
先自定义几个轻量注解,用于描述表名、主键、列名等:
- @Table:标注在类上,指定对应数据库表名;
- @Id:标注在字段上,标识主键(支持自增或手动赋值);
- @Column:标注在字段上,指定列名(可选,默认用字段名);
- 不强制要求 getter/setter,直接用
Field.setAccessible(true)访问私有字段。
封装通用 SQL 构建与参数绑定逻辑
根据实体类信息动态拼接 SQL,并用 PreparedStatement 安全设参:
- 查单个对象:解析所有非空字段(或按条件字段),生成
WHERE子句; - 插入:提取所有非
@Id字段(或带insertable=true的字段),生成INSERT INTO ... VALUES(?); - 更新:以
@Id字段为 WHERE 条件,其余非主键字段为 SET 内容; - 用
ArrayList<Object>收集参数值,按字段反射顺序与?占位符一一对应。
利用反射读写字段值,实现对象 ↔ 结果集互转
查询结果转对象时,遍历 ResultSetMetaData 获取列名,再匹配实体字段(忽略大小写或按 @Column 显式指定):
立即学习“Java免费学习笔记(深入)”;
- 对每个列,查找对应字段(优先用
@Column.name(),否则用列名转驼峰); - 调用
field.set(obj, resultSet.getObject(i))赋值,自动处理基本类型包装类转换; - 插入/更新前,遍历字段获取值:
field.get(entity),跳过 null 值或主键(插入时); - 注意处理时间类型(
java.util.Date→java.sql.Timestamp)和布尔类型兼容性。
BaseDao 核心方法示例(简化版)
以下是一个典型 save 方法骨架(使用 HikariCP 管理连接):
public <T> int save(T entity) throws SQLException {
Class<T> clazz = (Class<T>) entity.getClass();
Table table = clazz.getDeclaredAnnotation(Table.class);
String tableName = table != null ? table.value() : clazz.getSimpleName().toLowerCase();
List<String> columns = new ArrayList<>();
List<Object> params = new ArrayList<>();
Field idField = null;
for (Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
if (f.isAnnotationPresent(Id.class)) {
idField = f;
continue;
}
Column col = f.getAnnotation(Column.class);
columns.add(col != null ? col.value() : f.getName());
try {
params.add(f.get(entity));
} catch (IllegalAccessException e) {
throw new SQLException("Failed to read field: " + f.getName(), e);
}
}
String sql = "INSERT INTO " + tableName + " (" + String.join(",", columns)
+ ") VALUES (" + String.join(",", Collections.nCopies(columns.size(), "?")) + ")";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
for (int i = 0; i 0) {
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) {
Object key = rs.getObject(1);
idField.setAccessible(true);
idField.set(entity, key);
}
}
}
return affected;
}
}


















