这里总结一下ModelMapper的使用方式,供大家参考
前言
项目中对象与对象赋值转换使用的频率非常的高,比如数据库表实体对象(Entity)与业务类对象(Model)之间的赋值传递,或者模型对象(Model)与视图对象(ViewModel)之间的赋值传递。
如果我们一个一个字段的赋值,将是非常繁琐并且毫无价值的重复工作,此时虽然我们可以自己通过反射提取个公共的方法来处理,但是更高效的方式是查看是否有第三方已经提供了比较成熟稳定的工具包,避免重复造轮子的工作。
在C#中我们一般使用AutoMapper作为对象转换工具(AutoMapper配置使用参考:https://blog.csdn.net/fly_duck/article/details/102605046)。
Java中也有类似的转换工具,比如:Dozer,Orika,MapStruct,JMapper,ModelMapper以及BeanUtils等,都可以实现对象之间的转换。 本问主要介绍SpringBoot项目中ModelMapper的配置以及使用,使用ModelMapper我们可以快速的解决对象与对象之间的映射转换。官方的介绍如下:
The goal of ModelMapper is to make object mapping easy, by automatically determining how one object model maps to another, based on conventions, in the same way that a human would - while providing a simple, refactoring-safe API for handling specific use cases.
ModelMapper的Github地址: https://github.com/modelmapper/modelmapper,官方地址:http://modelmapper.org/,官方使用文档:http://modelmapper.org/user-manual/。
pom.xml配置
首先在pom.xml引入ModelMapper依赖:
<!-- https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.9</version>
</dependency>
ModelMapper配置
在SpringBoot项目中将ModelMapper配置好后注册为JavaBean交给Spring来管理,使用的时候直接通过注解获取ModelMapper实例来对对象进行转换。
项目中强烈建议将setFullTypeMatchingRequired设置为true使用完全匹配模式,将setMatchingStrategy设置为MatchingStrategies.STRICT使用严格匹配模式,避免字段名缺失被相似字段转换错误的情况。
package com.flyduck.mybatis.config;
import com.flyduck.mybatis.entity.User;
import com.flyduck.mybatis.model.UserModel;
import org.modelmapper.AbstractConverter;
import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.text.SimpleDateFormat;
import java.util.Date;
@Configuration
public class ModelMapperConfig {
private Converter<Date, String> dateToStringConverter = new AbstractConverter<Date, String>() {
@Override
protected String convert(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return date == null ? null : simpleDateFormat.format(date);
}
};
/**
* 将ModelMapper注册到Spring中
* 可以添加一些定制化的配置,官方文档:http://modelmapper.org/user-manual/property-mapping/
*
* @author flyduck
* @date 2020/11/25 21:35
* @param []
* @return org.modelmapper.ModelMapper
*/
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
// 官方配置说明: http://modelmapper.org/user-manual/configuration/
// 完全匹配
modelMapper.getConfiguration().setFullTypeMatchingRequired(true);
// 匹配策略使用严格模式
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.addConverter(dateToStringConverter);
configureUser(modelMapper);
return modelMapper;
}
private void configureUser(ModelMapper modelMapper) {
modelMapper.typeMap(UserModel.class, User.class)
.addMappings(mapper -> mapper.skip(User::setPassword))
.addMappings(mapper -> mapper.skip(User::setCreateTime))
.addMappings(mapper -> mapper.skip(User::setUpdateTime))
.addMappings(mapper -> mapper.map(UserModel::getName, User::setRealName));
modelMapper.typeMap(User.class, UserModel.class)
.addMappings(mapper -> mapper.skip(UserModel::setPassword))
.addMappings(mapper -> mapper.map(User::getRealName, UserModel::setName));
// .addMappings(mapper -> mapper.using(dateToStringConverter).map(User::getCreateTime, UserModel::setCreateTime))
// .addMappings(mapper -> mapper.using(dateToStringConverter).map(User::getUpdateTime, UserModel::setUpdateTime));
}
}
默认情况下,ModelMapper会通过反射按照字段名自动进行对象转换。
但是一些特殊情况,需要我们自定义配置,示例配置中我们单独为User和UserModel类之间的转换做了一些常用的配置。 当某个字段不需要转换可以通过skip来设置,当字段名不一致可以通过map来设置,当字段值需要处理后再转换可以通过Converter结合map来设置。
配置2
ModelMapperConfig.java
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.typeMap(Order.class, OrderDTO.class).addMappings(mapper -> {
mapper.map(src -> src.getBillingAddress().getStreet(),
Destination::setBillingStreet);
mapper.map(src -> src.getBillingAddress().getCity(),
Destination::setBillingCity);
});
return modelMapper;
}
}
MatchResult匹配模式
MatchResult有三种结果:FULL、PARTIAL和NONE(即全部匹配,部分匹配和不匹配)。
注意,这里有一个部分匹配,也就是容易踩到的坑。在对like进行匹配时,likeNum就被定义为部分匹配。因此,当likeNum大于2时,就不能被转换成boolean类型。
这里解决方法有两种,一种是在设置中,规定必须字段名完全匹配;另一种就是将匹配策略定义为严格。
设置方法如下:
modelMapper.getConfiguration().setFullTypeMatchingRequired(true); modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
例子1
User类的定义
public class User implements Serializable {
private Long id;
private String code;
private Date createTime;
private Date updateTime;
private Boolean isDelete;
private String userName;
private String email;
private String phoneNumber;
private String password;
private String realName;
private String nickName;
private String avatar;
// 省略getter, setter方法
}
UserModel的定义:
public class UserModel {
private Long id;
private String createTime;
private String updateTime;
private String userName;
private String password;
private String email;
private String phoneNumber;
private String name;
private String nickName;
private String avatar;
// 省略 getter,setter方法
}
对象转换
在Controller控制器中,通过@Autowired注解得到ModelMapper实例,然后直接通过modelMapper.map(user, UserModel.class)转换得到VO层的UserModel对象,然后直接传给前端接口。
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserMapper userMapper;
@Autowired
private ModelMapper modelMapper;
@GetMapping("{id}")
public UserModel get(@PathVariable Long id) {
User user = userMapper.selectByPrimaryKey(id);
if (user == null || user.getIsDelete()) {
return null;
}
UserModel userModel = modelMapper.map(user, UserModel.class);
return userModel;
}
}
例子2
假设JPA Entity:
@Data @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "posts", uniqueConstraints = {@UniqueConstraint(columnNames = {"title"})}) public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "title") private String title; @Column(name = "description") private String description; @Column(name = "content") private String content; } |
定义DTO对象:PostDto.java
@Data public class PostDto { private long id; private String title; private String description; private String content; } |
不使用ModelMapper库的代码,需要手工一个个字段转:
Post post = postRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Post", "id", id)); post.setTitle(postRequest.getTitle()); post.setDescription(postRequest.getDescription()); post.setContent(postRequest.getContent()); return postRepository.save(post); |
使用ModelMapper库的代码:
Post post = postService.getPostById(id);
// convert entity to DTO
PostDto postResponse = modelMapper.map(post, PostDto.class);
return ResponseEntity.ok().body(postResponse);
|
注意的是:modelMapper需要在配置为一个Bean才能被注入使用:
@SpringBootApplication public class SpringbootBlogApiApplication { @Bean public ModelMapper modelMapper() { return new ModelMapper(); } public static void main(String args) { SpringApplication.run(SpringbootBlogApiApplication.class, args); } } |
实体和DTO双向转换:
// convert DTO to entity Post postRequest = modelMapper.map(postDto, Post.class); Post post = postService.createPost(postRequest); // convert entity to DTO PostDto postResponse = modelMapper.map(post, PostDto.class); |
总结
以上就是SpringBoot项目中ModelMapper的常规使用,示例源码:https://gitee.com/flyduck128/springboot-demo/tree/master/flyduck-mybatis。
使用ModelMapper可以大大提高开发人员对于对象转换的开发效率,特别适用于微服务中对象之间的转换。对于ModelMapper的详细配置以及高级使用可以参考官方文档。
就如开篇所说的Java中对象转换的工具很多,ModelMapper常规使用配置简单,上手容易,同时也提供一些高级用法,同时由于使用的反射进行转换所以效率不是很高,对于效率要求不高的项目推荐使用ModelMapper。
拓展
国外朋友专门对Dozer,Orika,MapStruct,JMapper和ModelMapper进行了性能测试,测试地址:https://www.baeldung.com/java-performance-mapping-frameworks。
测试结果为JMapper和MapStruct性能表现最好。
参考
SpringBoot项目中ModelMapper配置以及使用_springboot modelmapper-CSDN博客
Java中ModelMapper 的高级使用_java_脚本之家