1、MapStruct是用来做什么的
在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。
这种对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,毕竟每一个字段都 get/set 会很麻烦。MapStruct 是一种属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。
2、案例
创建实体 user.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String userId;
private String name;
private Integer age;
private String email;
private Date birthDate;
}
创建实体VO userVo.java
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserVo {
private String name;
private Integer age;
private String email;
private Date birth;
private String birthformat;
}
###创建Mapper UserConverterMapper.java
可以看到UserConverterMapper是一个接口的形式存在的,当然也可以是一个抽象类,如果你需要在转换的时候才用个性化的定制的时候可以采用抽象类的方式,相应的代码配置官方文档已经声明。
@Mapper注解是用于标注接口、抽象类是被MapStruct自动映射的标识,只有存在该注解才会将内部的接口方法自动实现。
@Mapper(componentModel = "spring")
public interface UserConverterMapper {
UserConverterMapper INSTANCE = Mappers.getMapper(UserConverterMapper.class);
User UserVo2User(UserVo userVo);
@Mappings({
//属性名不一致映射
@Mapping(source = "birthdate", target = "birth"),
//属性格式不一致
@Mapping(target = "birthformat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthdate(),\"yyyy-MM-dd HH:mm:ss\"))"),
})
UserVo User2UserVo(User user);
List<User> UsersVo2Users(List<UserVo> userVos);
List<UserVo> Users2UserVos(List<User> user);
}
在Mapper接口定义方法上面声明了一系列的注解映射@Mapping以及@Mappings,那么这两个注解是用来干什么工作的呢?
@Mapping注解我们用到了两个属性,分别是source、target,参数名称和参数类型不一致的时候,可以使用映射参数关系。
source代表的是映射接口方法内的参数名称,如果是基本类型的参数,参数名可以直接作为source的内容,如果是实体类型,则可以采用实体参数名.字段名的方式作为source的内容,配置如上面UserConverterMapper内容所示。
target代表的是映射到方法方法值内的字段名称,配置如上面UserConverterMapper所示。
###Mapper的实现
MapStruct根据我们配置的@Mapping注解自动将source实体内的字段进行了调用target实体内字段的setXxx方法赋值,并且做出了一切参数验证。
@Component
public class UserConverterMapperImpl implements UserConverterMapper {
public UserConverterMapperImpl() {
}
public User UserVo2User(UserVo userVo) {
if (userVo == null) {
return null;
} else {
User user = new User();
user.setBirthDate(userVo.getBirth());
user.setName(userVo.getName());
user.setAge(userVo.getAge());
user.setEmail(userVo.getEmail());
return user;
}
}
public UserVo User2UserVo(User user) {
if (user == null) {
return null;
} else {
UserVo userVo = new UserVo();
userVo.setBirth(user.getBirthDate());
userVo.setName(user.getName());
userVo.setAge(user.getAge());
userVo.setEmail(user.getEmail());
userVo.setBirthformat(DateFormatUtils.format(user.getBirthDate(), "yyyy-MM-dd HH:mm:ss"));
return userVo;
}
}
public List<User> UsersVo2Users(List<UserVo> userVos) {
if (userVos == null) {
return null;
} else {
List<User> list = new ArrayList(userVos.size());
Iterator var3 = userVos.iterator();
while(var3.hasNext()) {
UserVo userVo = (UserVo)var3.next();
list.add(this.UserVo2User(userVo));
}
return list;
}
}
public List<UserVo> Users2UserVos(List<User> user) {
if (user == null) {
return null;
} else {
List<UserVo> list = new ArrayList(user.size());
Iterator var3 = user.iterator();
while(var3.hasNext()) {
User user1 = (User)var3.next();
list.add(this.User2UserVo(user1));
}
return list;
}
}
}
###测试和使用
测试验证:获取Mapper的方式就是采用Mappers通过动态工厂内部反射机制完成Mapper实现类的获取。
public class ConverterTest {
public static void main(String[] args) {
UserVo userVo = new UserVo("张三",22,"1212@qq.com",new Date(),null);
User user = UserConverterMapper.INSTANCE.UserVo2User(userVo);
Assert.assertTrue(userVo.getName().equals(user.getName()));
Assert.assertTrue(userVo.getAge().equals(user.getAge()));
UserVo vo = UserConverterMapper.INSTANCE.User2UserVo(user);
Assert.assertTrue(vo.getName().equals(user.getName()));
Assert.assertTrue(vo.getAge().equals(user.getAge()));
}
}
使用:在@Mapper注解内添加componentModel属性值,配置后在外部可以采用@Autowired方式注入Mapper实现类完成映射方法调用,由mapstruct自动生成的类文件,会发现标记了@Component注解。
@Autowired
public UserConverterMapper userConverterMapper;
@RequestMapping(value = "/fetchUsers",method = RequestMethod.GET)
@ResponseBody
public BaseResult<List<UserVo>> fetchBatch(String name){
BaseResult<List<UserVo>> result = new BaseResult();
try{
UserCondition condtion = new UserCondition();
condtion.setName(name);
List<User> userList = userService.fetchUserList(condtion);
List<UserVo> vos =this.userConverterMapper.Users2UserVos(userList);
result.setCode(0);
result.setMess("succ");
result.setData(vos);
return result;
}catch (Exception e){
return result;
}
}
调用返回结果
{"code":0,
"mess":"succ",
"data":[
{
"name":"小米",
"email":"1212@qq.com",
"birth":"2019-08-14 03:34:56",
"age":22
}]
}
3、总结
- 它可以自动封装一些同级,属性名相同的属性名如上name age 属性,可以无需手动写 @Mapping
- 对于非同级或属性名 需要写相关的 @mapping 属性名不同可以查考 birth 属性配置。如果不同级@Mapping(source = “XXX.birthDate”, target = “XX.birth”) 如此就行了。
@mapping 还有很多属性,可以查看API。