如何自定义ModelMapper

新手上路,请多包涵

我想使用 ModelMapper 将实体转换为 DTO 并返回。大多数情况下它有效,但我如何自定义它。它有太多的选择,以至于很难弄清楚从哪里开始。什么是最佳实践?

我会在下面自己回答,但如果另一个答案更好,我会接受。

原文由 John Henckel 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

首先这里有一些链接

我对mm的印象是它的设计非常好。代码很扎实,读起来很愉快。但是,文档非常简洁,示例很少。该 api 也令人困惑,因为似乎有 10 种方法可以做任何事情,并且没有任何迹象表明您为什么要这样做。

有两种选择: Dozer 最受欢迎, Orika 因其 易用性而获得好评。

假设您仍然想使用 mm,这就是我所了解的。

主类 ModelMapper 应该是您应用中的单例。对我来说,这意味着使用 Spring 的 @Bean。对于简单的情况,它开箱即用。例如,假设您有两个类:

 class DogData
{
    private String name;
    private int mass;
}

class DogInfo
{
    private String name;
    private boolean large;
}

使用适当的吸气剂/吸气剂。你可以这样做:

     ModelMapper mm = new ModelMapper();
    DogData dd = new DogData();
    dd.setName("fido");
    dd.setMass(70);
    DogInfo di = mm.map(dd, DogInfo.class);

并且“名称”将从dd复制到di。

自定义 mm 的方法有很多种,但首先你需要了解它是如何工作的。

mm 对象包含每个有序类型对的 TypeMap,例如 将是两个 TypeMap。

每个 TypeMap 都 包含一个带有映射列表的 PropertyMap 。因此,在示例中,mm 将自动创建一个 TypeMap,其中包含具有单个映射的 PropertyMap。

我们可以这样写

    TypeMap<DogData, DogInfo> tm = mm.getTypeMap(DogData.class, DogInfo.class);
    List<Mapping> list = tm.getMappings();
    for (Mapping m : list)
    {
        System.out.println(m);
    }

它会输出

PropertyMapping[DogData.name -> DogInfo.name]

当您调用 mm.map() 时,这就是它的作用,

  1. 查看 TypeMap 是否 存在,如果不存在,则为 源/目标类型创建 TypeMap
  2. 调用 TypeMap Condition ,如果它返回 FALSE,则什么都不做并停止
  3. 如有必要,调用 TypeMap Provider 构造新的目标对象
  4. 调用 TypeMap PreConverter( 如果有的话)
  5. 执行以下操作之一:
    • 如果 TypeMap 有一个 自定义的 Converter ,调用它
    • 或者,生成一个 PropertyMap (基于 配置标志 加上添加的任何 自定义映射),并使用它(注意:TypeMap 也有可选的自定义 Pre/PostPropertyConverters, 我认为 它会在 每次映射 前后运行。)
  6. 调用 TypeMap PostConverter( 如果有)

警告:这个流程图 有点记录, 但我不得不猜测很多,所以它可能不完全正确!

您可以自定义此过程的 _每个步骤_。但最常见的两种是

  • 步骤 5a。 – 编写自定义 TypeMap 转换器,或
  • 步骤 5b。 – 编写自定义属性映射。

以下是 自定义 TypeMap 转换器 的示例:

     Converter<DogData, DogInfo> myConverter = new Converter<DogData, DogInfo>()
    {
        public DogInfo convert(MappingContext<DogData, DogInfo> context)
        {
            DogData s = context.getSource();
            DogInfo d = context.getDestination();
            d.setName(s.getName());
            d.setLarge(s.getMass() > 25);
            return d;
        }
    };

    mm.addConverter(myConverter);

注意 转换器是 _单向的_。如果要将 DogInfo 自定义为 DogData,则必须编写另一个。

这是 自定义 PropertyMap 的示例:

     Converter<Integer, Boolean> convertMassToLarge = new Converter<Integer, Boolean>()
    {
        public Boolean convert(MappingContext<Integer, Boolean> context)
        {
            // If the dog weighs more than 25, then it must be large
            return context.getSource() > 25;
        }
    };

    PropertyMap<DogData, DogInfo> mymap = new PropertyMap<DogData, DogInfo>()
    {
        protected void configure()
        {
            // Note: this is not normal code. It is "EDSL" so don't get confused
            map(source.getName()).setName(null);
            using(convertMassToLarge).map(source.getMass()).setLarge(false);
        }
    };

    mm.addMappings(mymap);

pm.configure 函数真的很时髦。这不是实际的代码。它是以某种方式被解释的虚拟 EDSL 代码。例如,setter 的参数是不相关的,它只是一个占位符。你可以在这里做很多事情,比如

  • 当(条件).map(getter).setter
  • when(condition).skip().setter——安全地忽略字段。
  • using(converter).map(getter).setter – 自定义字段转换器
  • with(provider).map(getter).setter – 自定义字段构造函数

请注意,自定义映射已 添加 到默认映射中,因此您 无需 指定

            map(source.getName()).setName(null);

在您的自定义 PropertyMap.configure() 中。

在这个例子中,我必须编写一个 转换器 来将整数映射到布尔值。在大多数情况下,这不是必需的,因为 mm 会自动将 Integer 转换为 String 等。

我听说您还可以 使用 Java 8 lambda 表达式创建映射。我试过了,但我想不通。

最终建议和最佳实践

默认情况下,mm 使用 MatchingStrategies.STANDARD 这很危险。它很容易选择错误的映射并导致奇怪的、难以发现的错误。如果明年其他人向数据库添加一个新列怎么办?所以不要这样做。确保使用严格模式:

     mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

始终编写单元测试并确保所有映射都经过验证。

     DogInfo di = mm.map(dd, DogInfo.class);
    mm.validate();   // make sure nothing in the destination is accidentally skipped

使用 mm.addMappings() 修复任何验证失败,如上所示。

将所有映射放在一个中心位置,在该位置创建 mm 单例。

原文由 John Henckel 发布,翻译遵循 CC BY-SA 3.0 许可协议

我在使用 ModelMapper 进行映射时遇到了问题。不仅属性而且我的源和目标类型都不同。我通过这样做解决了这个问题 - >

如果源和目标类型不同。例如,

 @Entity
class Student {
    private Long id;

    @OneToOne
    @JoinColumn(name = "laptop_id")
    private Laptop laptop;
}

和 Dto ->

 class StudentDto {
    private Long id;
    private LaptopDto laptopDto;
}

此处,源和目标类型不同。因此,如果您的 MatchingStrategies 是严格的,您将无法在这两种不同类型之间进行映射。现在要解决这个问题,只需将下面的代码放在您的控制器类或您要使用 ModelMapper-> 的任何类的构造函数中

private ModelMapper modelMapper;

public StudentController(ModelMapper modelMapper) {
    this.modelMapper = modelMapper;
    this.modelMapper.typeMap(Student.class, StudentDto.class).addMapping(Student::getLaptop, StudentDto::setLaptopDto);
}


而已。现在您可以轻松使用 ModelMapper.map(source, destination) 。它会自动映射

modelMapper.map(student, studentDto);

原文由 Muhammad Saimon 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题