如何利用MapStruct的@Context与qualifiedByName实现ResultSet到DTO的映射(替代expression方案)
如何利用MapStruct的@Context与qualifiedByName实现ResultSet到DTO的映射(替代expression方案)
嗨,我太懂你那种看着一堆嵌套引号的expression写法就头大的感觉了!硬编码列名不说,代码可读性拉胯,还不好维护。既然MapStruct官方不打算直接支持ResultSet映射,那咱们就用@Context和qualifiedByName整个干净又可复用的方案,完美替代丑陋的expression写法。
先说说你之前尝试失败的原因:你把ResultSet当成了源对象来映射,MapStruct会默认去寻找源对象的getter方法或者字段,但ResultSet根本没有对应DTO字段的getter(比如getSource()),自然会报错。咱们要换个思路:把ResultSet当成上下文对象传递,通过自定义的提取方法来拿数据,而不是让MapStruct直接解析它。
下面是具体的实现步骤:
第一步:创建ResultSet上下文访问类
先写一个专门用来从ResultSet提取数据的辅助类,把所有数据库字段的提取逻辑封装在这里,还能统一处理列名映射、异常这些细节:
public class ResultSetContext { private final ResultSet rs; // 可选:如果DTO属性名和数据库列名不一致,这里可以加个映射表 private final Map<String, String> propToColumnMap; // 构造方法:如果不需要列名映射,只传ResultSet就行 public ResultSetContext(ResultSet rs) { this.rs = rs; this.propToColumnMap = Collections.emptyMap(); } // 带列名映射的构造方法(可选,按需使用) public ResultSetContext(ResultSet rs, Map<String, String> propToColumnMap) { this.rs = rs; this.propToColumnMap = propToColumnMap; } // 提取String类型的方法,用@Named标记,方便Mapper里指定使用 @Named("getString") public String getString(@TargetProperty String targetPropName) throws SQLException { // 优先用列名映射,没有的话直接用属性名当列名 String columnName = propToColumnMap.getOrDefault(targetPropName, targetPropName); return rs.getString(columnName); } // 同理,扩展其他数据类型的提取方法 @Named("getInt") public Integer getInt(@TargetProperty String targetPropName) throws SQLException { String columnName = propToColumnMap.getOrDefault(targetPropName, targetPropName); Integer value = rs.getInt(columnName); // 处理JDBC的NULL值(如果需要的话) return rs.wasNull() ? null : value; } // 可以继续加getLong、getTimestamp等方法,覆盖你需要的所有数据类型 }
第二步:编写Mapper接口
在Mapper里,我们不再把ResultSet当源对象,而是通过@Context注解把它(或者上面的ResultSetContext)传进去,然后用qualifiedByName指定每个DTO字段要用哪个提取方法:
@Mapper public interface NameValuePairMapper { // 核心映射方法:用@Context注入ResultSetContext @Mapping(target = "name", qualifiedByName = "getString") @Mapping(target = "value", qualifiedByName = "getString") NameValuePair map(@Context ResultSetContext resultSetContext) throws SQLException; // 提供一个便捷方法:直接接收ResultSet,自动包装成上下文对象(不用上层代码手动处理) default NameValuePair map(ResultSet rs) throws SQLException { return map(new ResultSetContext(rs)); } // 可选:如果需要自定义列名映射,再加一个便捷方法 default NameValuePair map(ResultSet rs, Map<String, String> propToColumnMap) throws SQLException { return map(new ResultSetContext(rs, propToColumnMap)); } }
第三步:使用Mapper
上层代码调用的时候就非常清爽了,完全不用关心底层的提取逻辑:
// 假设你已经有一个ResultSet实例rs NameValuePairMapper mapper = Mappers.getMapper(NameValuePairMapper.class); NameValuePair dto = mapper.map(rs); // 如果需要列名映射(比如DTO的name对应数据库的user_name列) Map<String, String> columnMap = new HashMap<>(); columnMap.put("name", "user_name"); NameValuePair dtoWithCustomMapping = mapper.map(rs, columnMap);
方案的优势
- 代码干净无嵌套:再也看不到
expression = "java(rs.getString(\"propName\"))"这种反人类写法 - 逻辑复用:所有ResultSet的提取逻辑都封装在
ResultSetContext里,改一处全生效 - 类型安全:通过
qualifiedByName指定提取方法,MapStruct会在编译期检查方法是否存在,避免运行时错误 - 灵活扩展:随时可以加新的提取方法,或者在
ResultSetContext里加缓存、列名映射、NULL值处理等逻辑
备注:内容来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

