You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:14:55