Joh*_*kel 8 java rest modelmapper
我想使用ModelMapper将实体转换为DTO并返回.它主要是有效的,但我如何自定义它.它有很多选择,很难弄清楚从哪里开始.什么是最佳做法?
我将在下面自己回答,但如果另一个答案更好,我会接受它.
Joh*_*kel 62
首先是一些链接
我对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;
}
Run Code Online (Sandbox Code Playgroud)
与适当的getter/setters.你可以这样做:
ModelMapper mm = new ModelMapper();
DogData dd = new DogData();
dd.setName("fido");
dd.setMass(70);
DogInfo di = mm.map(dd, DogInfo.class);
Run Code Online (Sandbox Code Playgroud)
并且"名称"将从dd复制到di.
有许多方法可以自定义mm,但首先您需要了解它的工作原理.
mm对象包含每个有序类型对的TypeMap,例如<DogInfo,DogData>和<DogData,DogInfo>将是两个TypeMaps.
每个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);
}
Run Code Online (Sandbox Code Playgroud)
它会输出
PropertyMapping[DogData.name -> DogInfo.name]
Run Code Online (Sandbox Code Playgroud)
当你调用mm.map()时,这就是它的作用,
警告:这个流程图有点记录,但我不得不猜测很多,所以它可能不正确!
您可以自定义此过程的每个步骤.但最常见的两个是
以下是自定义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);
Run Code Online (Sandbox Code Playgroud)
请注意,转换器是单向的.如果要将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);
Run Code Online (Sandbox Code Playgroud)
pm.configure功能非常时髦.这不是实际的代码.它是虚拟EDSL代码,以某种方式解释.例如,setter的参数不相关,它只是一个占位符.你可以在这里做很多事情,比如
注意:映射自定义添加到默认的映射,这样你就不会需要,例如,指定
map(source.getName()).setName(null);
Run Code Online (Sandbox Code Playgroud)
在自定义PropertyMap.configure()中.
在这个例子中,我不得不编写一个转换器来将Integer映射到Boolean.在大多数情况下,这不是必需的,因为mm会自动将Integer转换为String等.
我被告知你也可以使用Java 8 lambda表达式创建映射.我试过了,但我无法弄明白.
最终建议和最佳实践
默认情况下mm使用MatchingStrategies.STANDARD哪个是危险的.它可以轻松选择错误的映射并导致奇怪,很难找到错误.如果明年其他人在数据库中添加新列怎么办?所以不要这样做.确保使用STRICT模式:
mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
Run Code Online (Sandbox Code Playgroud)
始终编写单元测试并确保所有映射都经过验证.
DogInfo di = mm.map(dd, DogInfo.class);
mm.validate(); // make sure nothing in the destination is accidentally skipped
Run Code Online (Sandbox Code Playgroud)
修复任何验证失败,mm.addMappings()如上所示.
将所有映射放在中心位置,即创建mm单例.
我在使用 ModelMapper 映射时遇到了一个问题。不仅属性不同,我的源和目标类型也不同。我通过这样做解决了这个问题->
如果源和目标类型不同。例如,
@Entity
class Student {
private Long id;
@OneToOne
@JoinColumn(name = "laptop_id")
private Laptop laptop;
}
Run Code Online (Sandbox Code Playgroud)
和 Dto ->
class StudentDto {
private Long id;
private LaptopDto laptopDto;
}
Run Code Online (Sandbox Code Playgroud)
在这里,源和目标类型是不同的。因此,如果您的 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);
}
Run Code Online (Sandbox Code Playgroud)
就是这样。现在您可以轻松使用 ModelMapper.map(source, destination)。它会自动映射
modelMapper.map(student, studentDto);
Run Code Online (Sandbox Code Playgroud)