如何用Java泛型与反射实现Spring SimpleJdbcCall通用Model映射
通用Model映射器实现方案(基于反射+Spring SimpleJdbcCall)
看起来你正在尝试构建一个基于反射的通用DAO层,来避免为每个Model编写重复的RowMapper代码——这个思路非常棒,能大幅减少冗余代码!我来帮你一步步解决反射实例化、属性映射和类型适配这三个核心问题,直接上可运行的实现方案:
一、核心思路拆解
我们需要在RowMapper的lambda实现里完成三件事:
- 通过反射创建目标Model实例
- 匹配ResultSet列与Model的Setter方法
- 根据属性类型调用ResultSet对应的取值方法
二、完整实现代码
首先补充DAO抽象类的实现,同时添加工具方法处理列名转换和类型映射:
@Repository public abstract class DAO<T extends ModelBase> { private final DataSource dataSource; // 类型映射表:Java属性类型 → ResultSet对应的取值方法名 private static final Map<Class<?>, String> TYPE_TO_RESULT_SET_METHOD = new HashMap<>(); static { // 初始化常用类型的映射关系 TYPE_TO_RESULT_SET_METHOD.put(String.class, "getString"); TYPE_TO_RESULT_SET_METHOD.put(Integer.class, "getInt"); TYPE_TO_RESULT_SET_METHOD.put(int.class, "getInt"); TYPE_TO_RESULT_SET_METHOD.put(Long.class, "getLong"); TYPE_TO_RESULT_SET_METHOD.put(long.class, "getLong"); TYPE_TO_RESULT_SET_METHOD.put(Boolean.class, "getBoolean"); TYPE_TO_RESULT_SET_METHOD.put(boolean.class, "getBoolean"); TYPE_TO_RESULT_SET_METHOD.put(java.util.Date.class, "getDate"); TYPE_TO_RESULT_SET_METHOD.put(java.sql.Date.class, "getDate"); // 可根据需求扩展:BigDecimal、Float、Double等类型 } DAO(final DataSource dataSource) { this.dataSource = dataSource; } protected List<T> buildModels(SqlParameterSource sqlSource, Class<T> modelClass) { final String RESULT_SET = "RESULT_SET"; return new SimpleJdbcCall(dataSource) .withCatalogName("someCatalog") .withSchemaName("someSchema") .withProcedureName(getProcForModel(modelClass)) .returningResultSet(RESULT_SET, (ResultSet rs, int rowNum) -> { try { // 1. 创建Model实例(假设Model有无参构造) T model = modelClass.getDeclaredConstructor().newInstance(); // 获取ResultSet元数据,遍历所有列 ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { String columnName = metaData.getColumnName(i); // 2. 将数据库列名(通常下划线格式)转为驼峰式属性名 String propertyName = convertColumnToProperty(columnName); // 找到对应的Setter方法 Method setterMethod = findSetterMethod(modelClass, propertyName); if (setterMethod == null) { // 无对应Setter时可跳过或打日志提示 continue; } // 3. 根据属性类型调用ResultSet对应的取值方法 Class<?> paramType = setterMethod.getParameterTypes()[0]; Object value = getResultSetValue(rs, columnName, paramType); // 调用Setter赋值 setterMethod.invoke(model, value); } return model; } catch (Exception e) { throw new RuntimeException("Failed to map ResultSet to Model: " + modelClass.getName(), e); } }).execute(sqlSource).get(RESULT_SET); } // 数据库列名转驼峰(例如USER_NAME → userName) private String convertColumnToProperty(String columnName) { if (columnName == null || columnName.isEmpty()) { return columnName; } StringBuilder sb = new StringBuilder(); boolean nextUpperCase = false; for (char c : columnName.toCharArray()) { if (c == '_') { nextUpperCase = true; } else { sb.append(nextUpperCase ? Character.toUpperCase(c) : Character.toLowerCase(c)); nextUpperCase = false; } } return sb.toString(); } // 查找对应属性的Setter方法,支持递归查找父类 private Method findSetterMethod(Class<?> modelClass, String propertyName) { String setterName = "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); try { // 遍历当前类的方法 for (Method method : modelClass.getDeclaredMethods()) { if (method.getName().equals(setterName) && method.getParameterCount() == 1) { return method; } } // 当前类找不到则递归查找父类 Class<?> superClass = modelClass.getSuperclass(); if (superClass != null && !superClass.equals(Object.class)) { return findSetterMethod(superClass, propertyName); } } catch (Exception e) { // 忽略异常,返回null } return null; } // 根据属性类型从ResultSet获取对应值,处理类型转换与空值 private Object getResultSetValue(ResultSet rs, String columnName, Class<?> targetType) throws SQLException { String methodName = TYPE_TO_RESULT_SET_METHOD.get(targetType); if (methodName == null) { // 未匹配类型默认用getString处理,也可抛出自定义异常 return rs.getString(columnName); } try { // 反射调用ResultSet的取值方法 Method method = ResultSet.class.getMethod(methodName, String.class); Object value = method.invoke(rs, columnName); // 处理基本类型空值:比如getInt返回0但实际数据库为null,包装类型转为null if (value != null && targetType.isPrimitive()) { return value; } // 特殊处理java.util.Date与java.sql.Date的转换 if (targetType.equals(java.util.Date.class) && value instanceof java.sql.Date) { return new java.util.Date(((java.sql.Date) value).getTime()); } return value == null ? null : value; } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new SQLException("Failed to get value from ResultSet for column: " + columnName, e); } } // 抽象方法:由子类提供Model对应的存储过程名 protected abstract String getProcForModel(Class<T> modelClass); }
三、关键细节说明
- Model实例化:这里假设所有Model子类都有无参构造,若使用带参构造,可修改
modelClass.getDeclaredConstructor(...)传入参数类型,再从ResultSet中获取对应值完成实例化。 - 列名与属性名映射:
convertColumnToProperty方法处理了常见的下划线转驼峰规则,若有自定义命名规则,直接修改该方法逻辑即可。 - 类型适配:
TYPE_TO_RESULT_SET_METHOD覆盖了常用Java类型,可根据业务需求扩展;同时处理了java.util.Date与java.sql.Date的类型转换,避免类型不匹配问题。 - 异常处理:将反射与JDBC操作异常统一包装为
RuntimeException,上层调用者可统一处理,也可改为自定义异常。
四、优化建议
- 缓存反射结果:用
Map<Class<?>, Map<String, Method>>缓存每个Model类的Setter方法,避免每次映射重复查找,提升性能。 - 注解支持:添加
@Column注解指定属性对应的数据库列名,增强灵活性(比如属性名与列名不一致时)。 - 构造函数注入:若不想依赖无参构造,可分析构造函数参数名与列名的对应关系,实现构造函数注入属性。
内容的提问来源于stack exchange,提问作者resu
相关产品推荐
相关产品推荐

