如何在ModelMapper中用STRICT策略实现Entity到ID的自动映射?
通用Entity到ID属性的ModelMapper STRICT策略映射解决方案
问题背景
需要实现ModelMapper STRICT策略下,自动将所有Entity子类属性映射为对应String类型ID属性的通用方案,无需为每一处映射手动编写配置。示例如下:
源模型:
public class Reward { private UserEntity owner; // UserEntity 继承自 Entity,Entity 包含 getId() 方法 }
目标模型:
public class RewardResponseModel { private String ownerId; }
手动配置的映射代码可正常运行,但此类场景过多,需通用化实现:
Converter<Entity, String> entityToEntityIdConverter = context -> { Entity entity = context.getSource(); return entity != null ? entity.getId() : null; }; modelMapper.typeMap(Reward.class, RewardResponseModel.class) .addMappings(mapper -> mapper.using(entityToEntityIdConverter) .map(Reward::getOwner, RewardResponseModel::setOwnerId));
要求支持嵌套类映射,曾尝试反射实现但未成功。
解决方案
方案一:全局转换器+反射批量配置TypeMap
通过全局注册转换器,结合反射自动识别符合规则的属性对,批量配置映射规则,同时递归处理嵌套类:
1. 注册全局Entity转ID转换器
Converter<Entity, String> entityIdConverter = ctx -> ctx.getSource() != null ? ctx.getSource().getId() : null; modelMapper.addConverter(entityIdConverter);
2. 实现通用映射配置方法
public static <S, D> void setupEntityIdMappings(ModelMapper modelMapper, Class<S> sourceClazz, Class<D> destClazz) { TypeMap<S, D> typeMap = modelMapper.createTypeMap(sourceClazz, destClazz); // 处理当前类的Entity属性映射 Arrays.stream(sourceClazz.getDeclaredFields()) .filter(field -> Entity.class.isAssignableFrom(field.getType())) .forEach(sourceField -> { String sourceFieldName = sourceField.getName(); String destFieldName = sourceFieldName + "Id"; try { Field destField = destClazz.getDeclaredField(destFieldName); if (destField.getType().equals(String.class)) { // 获取getter和setter方法 Method sourceGetter = sourceClazz.getMethod("get" + capitalize(sourceFieldName)); Method destSetter = destClazz.getMethod("set" + capitalize(destFieldName), String.class); // 添加映射规则 typeMap.addMappings(mapper -> mapper .using(entityIdConverter) .map(sourceObj -> sourceGetter.invoke(sourceObj), (destObj, id) -> { try { destSetter.invoke(destObj, id); } catch (Exception e) { throw new RuntimeException("映射调用失败", e); } })); } } catch (NoSuchFieldException | NoSuchMethodException e) { // 无匹配目标属性,跳过 } }); // 递归处理嵌套类属性 Arrays.stream(sourceClazz.getDeclaredFields()) .filter(field -> !field.getType().isPrimitive() && !String.class.equals(field.getType()) && !Entity.class.isAssignableFrom(field.getType())) .forEach(nestedField -> { try { String nestedFieldName = nestedField.getName(); Field destNestedField = destClazz.getDeclaredField(nestedFieldName); // 递归配置嵌套类的映射 setupEntityIdMappings(modelMapper, nestedField.getType(), destNestedField.getType()); } catch (NoSuchFieldException e) { // 无对应嵌套目标类,跳过 } }); } // 辅助工具方法:将字符串首字母大写 private static String capitalize(String str) { if (str == null || str.isBlank()) return str; return str.substring(0, 1).toUpperCase() + str.substring(1); }
3. 配置ModelMapper为STRICT策略
modelMapper.getConfiguration() .setMatchingStrategy(MatchingStrategy.STRICT) .setFieldMatchingEnabled(true) .setFieldAccessLevel(AccessLevel.PRIVATE);
4. 使用方式
setupEntityIdMappings(modelMapper, Reward.class, RewardResponseModel.class);
方案二:自定义NamingStrategy(更简洁)
若属性命名严格遵循源属性名对应源属性名+Id的规则,可通过自定义NamingStrategy让ModelMapper自动识别映射关系,结合全局转换器即可实现:
// 配置自定义命名策略 modelMapper.getConfiguration().setNamingStrategy(new NamingStrategy() { @Override public String translate(Nameable nameable) { String name = nameable.getName(); // 针对目标属性,将xxxId转换为xxx,匹配源属性名 if (nameable instanceof PropertyInfo && ((PropertyInfo) nameable).getDirection() == PropertyInfo.Direction.OUT) { if (name.endsWith("Id")) { return name.substring(0, name.length() - 2); } } return name; } }); // 注册全局转换器(同方案一) Converter<Entity, String> entityIdConverter = ctx -> ctx.getSource() != null ? ctx.getSource().getId() : null; modelMapper.addConverter(entityIdConverter); // 配置STRICT策略 modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategy.STRICT);
这种方式无需手动遍历属性,ModelMapper会自动匹配owner到ownerId,同时支持嵌套类映射。
内容的提问来源于stack exchange,提问作者Kumin
相关产品推荐
相关产品推荐

