26

我想使用 ModelMapper 将实体转换为 DTO 并返回。大多数情况下它可以工作,但我该如何定制它。它有很多选择,很难弄清楚从哪里开始。什么是最佳做法?

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

4

4 回答 4

99

首先这里有一些链接

我对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;
}

使用适当的 getter/setter。你可以这样做:

    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,例如 <DogInfo, DogData> 和 <DogData, DogInfo> 将是两个 TypeMap。

每个TypeMap 都包含一个带有映射列表的PropertyMap 。因此在示例中,mm 将自动创建一个 TypeMap<DogData, DogInfo> ,其中包含一个具有单个映射的 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 是否存在,如果不存在,则为 <S, D> 源/目标类型创建 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的示例:

    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 代码。例如,设置器的参数不相关,它只是一个占位符。你可以在这里做很多事情,比如

  • 何时(条件).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危险的。它很容易选择错误的映射并导致奇怪的、难以发现的错误。如果明年其他人在数据库中添加一个新列怎么办?所以不要这样做。确保使用 STRICT 模式:

    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 单例的中心位置。

于 2017-06-14T01:54:43.097 回答
10

我在使用 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 是 STRICT,您将无法在这两种不同类型之间进行映射。现在要解决这个问题,只需简单地将下面的代码放在控制器类的构造函数中或任何要使用 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);
于 2020-06-24T17:58:52.360 回答
3

我从过去 6 个月开始一直在使用它,我将解释我对此的一些想法:

首先,建议将其用作唯一实例(singleton,spring bean,...),这在手册中有说明,我认为所有人都同意这一点。

ModelMapper是一个伟大的映射库和广泛的灵活性。由于它的灵活性,有很多方法可以获得相同的结果,这就是为什么它应该在最佳实践手册中说明何时使用一种或其他方式来做同样的事情。

开始ModelMapper有点困难,它的学习曲线非常紧凑,有时很难理解做某事的最佳方法,或者如何做其他事情。因此,首先需要准确地阅读和理解手册。

您可以使用以下设置根据需要配置映射:

Access level
Field matching
Naming convention
Name transformer
Name tokenizer 
Matching strategy

默认配置是最好的(http://modelmapper.org/user-manual/configuration/),但如果你想自定义它,你可以做到。

只是与匹配策略配置有关的一件事,我认为这是最重要的配置,需要小心。我会使用StrictorStandard但从不使用Loose,为什么?

  • Due Loose 是最灵活和最智能的映射器,它可以映射一些您无法预料的属性。所以,绝对要小心。我认为最好创建自己的 PropertyMap 并在需要时使用 Converters,而不是将其配置为 Loose。

否则,对validate所有属性匹配很重要,您验证它是否有效,并且使用 ModelMapper 更需要智能映射,因为它是通过反射完成的,因此您将没有编译器帮助,它将继续编译,但映射将失败意识到它。这是我最不喜欢的事情之一,但它需要避免样板文件和手动映射。

最后,如果你确定在你的项目中使用 ModelMapper,你应该按照它建议的方式使用它,不要将它与手动映射混合(例如),只需使用 ModelMapper,如果你不知道该怎么做肯定是可能的(调查,...)。有时使用模型映射器(我也不喜欢它)很难像手动那样做,但这是您应该付出的代价,以避免在其他 POJO 中进行样板映射。

于 2017-06-14T10:21:36.593 回答
1
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class EntityDtoConversionUtil {

    @Autowired
    private ModelMapper modelMapper;

    public Object convert(Object object,Class<?> type) {

        Object MapperObject=modelMapper.map(object, type);

        return MapperObject;

    }


}
于 2020-02-16T14:31:00.097 回答