首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何定制ModelMapper

如何定制ModelMapper
EN

Stack Overflow用户
提问于 2017-06-14 01:54:43
回答 4查看 75.5K关注 0票数 29

我想使用ModelMapper将实体转换为DTO和back。主要是它的工作,但我如何定制它。它有太多的选择,所以很难确定从哪里开始。什么是最佳实践?

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2017-06-14 01:54:43

首先,这里有一些链接

  • 模型映射器启动
  • api文档
  • 博客帖子
  • 随机码例

我对mm的印象是它设计得很好。代码是坚实的和愉快的阅读。然而,文档非常简洁,很少有例子。另外,api令人困惑,因为似乎有10种方法可以做任何事情,并且没有任何迹象表明为什么您会这样或那样做。

有两种选择:杜泽尔是最受欢迎的,奥里卡为了便于使用得到了很好的评价。

假设你还想使用mm,下面是我所了解到的。

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

代码语言:javascript
复制
class DogData
{
    private String name;
    private int mass;
}

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

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

代码语言:javascript
复制
    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,例如和将是两个TypeMaps。

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

我们可以写这个

代码语言:javascript
复制
    TypeMap<DogData, DogInfo> tm = mm.getTypeMap(DogData.class, DogInfo.class);
    List<Mapping> list = tm.getMappings();
    for (Mapping m : list)
    {
        System.out.println(m);
    }

它将输出

代码语言:javascript
复制
PropertyMapping[DogData.name -> DogInfo.name]

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

  1. 查看 TypeMap 是否还存在,如果没有为源/目标类型创建TypeMap
  2. 调用TypeMap 条件,如果它返回FALSE,则不执行任何操作并停止
  3. 如果需要,调用TypeMap Provider来构造一个新的目标对象
  4. 如果有PreConverter,则调用TypeMap
  5. 执行下列操作之一:
    • 如果TypeMap有一个自定义转换器,请调用它
    • 或者,生成一个PropertyMap (基于配置标志加上添加的任何自定义映射),并使用它(注意: TypeMap还有可选的自定义Pre/PostProperty转换器,我认为在每次映射之前和之后都会运行这些转换程序)。

  1. 如果有PostConverter,则调用TypeMap

注意:这个流程图是有一定的记录,但是我不得不猜测很多,所以它可能不是全部正确的!

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

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

下面是一个自定义TypeMap转换器的示例

代码语言:javascript
复制
    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的示例

代码语言:javascript
复制
    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
  • 当(条件).skip().setter-安全忽略字段。
  • 使用(转换器).map(Getter).setter-自定义场转换器
  • 使用(提供者).map(Getter).setter-自定义字段构造函数

注释自定义映射被添加到默认映射中,因此您不需要例如指定

代码语言:javascript
复制
            map(source.getName()).setName(null);

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

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

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

最终建议和最佳实践

默认情况下,mm使用MatchingStrategies.STANDARD,这是危险的。它可以很容易地选择错误的映射,并导致奇怪,很难找到错误。如果明年其他人在数据库中添加一个新的列,又会怎样呢?所以别这么做。确保使用严格的模式:

代码语言:javascript
复制
    mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

始终编写单元测试,并确保所有映射都得到验证。

代码语言:javascript
复制
    DogInfo di = mm.map(dd, DogInfo.class);
    mm.validate();   // make sure nothing in the destination is accidentally skipped

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

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

票数 105
EN

Stack Overflow用户

发布于 2020-06-24 17:58:52

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

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

代码语言:javascript
复制
@Entity
class Student {
    private Long id;
    
    @OneToOne
    @JoinColumn(name = "laptop_id")
    private Laptop laptop;
}

和Dto ->

代码语言:javascript
复制
class StudentDto {
    private Long id;
    private LaptopDto laptopDto;
}

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

代码语言:javascript
复制
private ModelMapper modelMapper;

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

就这样。现在您可以轻松地使用ModelMapper.map(源,目的地)。它会自动映射

代码语言:javascript
复制
modelMapper.map(student, studentDto);
票数 11
EN

Stack Overflow用户

发布于 2017-06-14 10:21:36

我从过去的6个月开始使用它,我要解释一下我的一些想法:

首先,建议将其作为惟一的实例(singleton,spring,.),手册中对此进行了解释,我认为大家都同意这一点。

ModelMapper是一个很好的映射库,具有很大的灵活性。由于它的灵活性,有许多方法可以获得相同的结果,这就是为什么应该在最佳实践手册中使用一种或另一种方法来做同样的事情。

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

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

代码语言:javascript
复制
Access level
Field matching
Naming convention
Name transformer
Name tokenizer 
Matching strategy

默认配置是最好的(http://modelmapper.org/user-manual/configuration/),但是如果您想定制它,您就可以这样做。

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

  • 由于松散是最灵活和智能的地图,它可以映射一些属性,你不能期待。所以,一定要小心。我认为最好创建自己的PropertyMap,并在需要时使用转换器,而不是将其配置为松散的。

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

最后,如果您确定要在项目中使用ModelMapper,您应该使用它建议的方式,不要将它与手动映射(例如)混合使用,只需使用ModelMapper,如果您不知道如何做某事--请确保是可能的(调查,.)。有时候,用模型映射器(我也不喜欢)手工完成它是很困难的,但这是在其他POJO中避免样板映射所应该付出的代价。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44534172

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档