深入使用MapStruct
前言
上一篇我们介绍了如何在java中进行对象的转换:juejin.cn/post/722518…。
其中我们重点提到了MapStruct这一工具。今天我们就来深入的了解一下MapStruct在开发中的常用功能。
功能
我们先来创建用于对象转换的两个类:
@Data
public static class UserVO {
private String fullName;
private int age;
private Extend extend;
}
@Data
public static class User {
private String name;
private int age;
private Extend extend;
}
@Data
private static class Extend {
private String email;
private String address;
private String phone;
private String company;
private String jobTitle;
private String education;
private String nationality;
private String gender;
}
1.普通映射
普通映射是将一个类映射到另外一个类上,使用简单。
如上述代码演示的两个类,我们需要把User中的属性全都复制到UserVO上去。当两个属性的名称不一致时,我们需要配置一下显示的配置一下映射关系,如下: @Mapping (source = "name", target = "fullName")。
@Mapper
public interface CommonUserMapper {
CommonUserMapper INSTANCE = Mappers.getMapper(CommonUserMapper.class);
@Mapping(source = "name", target = "fullName")
UserVO toUserVO(User user);
}
其运行代码如下:UserVO userVO = CommonUserMapper.INSTANCE.toUserVO(user) 。 从运行时变量的结果截图来看,userVO对象中的extend属性和原始User中的extend的对象是同一个,说明我们在不做其他的操作时,
MapStruct对类中的对象属性是浅拷贝。
要实现深度拷贝有以下办法:
- 新建一个和Extend类含有相同属性的新类。比如下图示例,我新建了一个ExtendVO类来进行属性的映射。
2.参数引用映射
MapStruct允许我们在配置映射关系时,可以配置普通参数。如下示例所示,我们将字符串参数email映射到
UserVO.Extend.email 上:
@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "email", target = "extend.email")
UserVO toUserVO(User user , String email);
这里要注意一下,当配置了参数时,在配置对象属性关系映射时,也要显示的指明对象参数的名称。如这里的
@Mapping(source = "user.name", target = "fullName") 就显示指明了 source = "user.name" 。
运行结果如下入所示:
3.多对象参数映射
这个映射其实和【参数引用映射】差不多,将多个实体组合成一个输出对象。
如下示例所示,我们将原始Extend对象映射到UserVO.ExtendVO上:
@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "extend", target = "extend")
UserVO toUserVO(User user , Extend extend);
这里的注意点和 【参数引用映射】差不多。
运行结果如下入所示:
从运行结果,我们看明白一件事:这种方式对于 Extend -> UserVO.ExtendVO 是属于深拷贝的。
4.嵌套释放
比如我们现在的User类,其内嵌了一个Extend类,现在我们需要把UserVO中的ExtendVO展开到UserVO中,变成一个平面类,那么我们改怎么配置呢?
@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "extend", target = ".")
UserVO toUserVO1(User user);
我们使用 “ . as target” ,来实现将一个嵌套的bean的值合并到一个扁平化的对象中。
运行结果如下入所示:
5.对象更新
上面所演示示例都是使用MapStruct给我们生成了一个对象,那么我们需要更新一个对象属性时该怎么配置呢?在业务场景中,我们可能更需要是只更新某个对象的某些属性,那么又该怎么配置呢?
@Mapping( target = "fullName" ,ignore = true)
@Mapping(source = "extend", target = ".")
void toUserVO2(User user, @MappingTarget UserVO userVO);
经过测试我们就可以回答上面的问题了,@MappingTarget 使用改注解标记目标对象我们就可以更新该对象的值。但是有一个问题,对象更新的策略和复制是一样的,所以如果你想不更新某个值,可以给加一个 ignore = true
的标签来忽略它。
6.集合、流、枚举映射
集合
MapStruct还可以支持集合类型,流,枚举等数据类型的映射关系。
Map示例配置(转换每个值和键的值)请注意一下这里使用的注解是MapMapping
:
@MapMapping(valueDateFormat = "dd.MM.yyyy") // 数据格式转换
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
我们通过反编译的方式可以看到其生成的class文件代码如下:
流
流的映射和集合映射类似,MapStruct通过分析自动完成对应的转换逻辑。
如下面的示例(将Integer流转换成字符串集合):
Set<String> integerStreamToStringSet(Stream<Integer> integers);
枚举
集合的转换比较繁琐,而且使用场景有限,用处并不是很大,了解一下即可(枚举的映射关系使用的是ValueMapping
)。下面采用一个官方的示例说明:
@ValueMapping(source = "EXTRA", target = "SPECIAL"),
@ValueMapping(source = "STANDARD", target = "DEFAULT"),
@ValueMapping(source = "NORMAL", target = "DEFAULT")
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
7.自定义
自定义注解
自定义注解的主要作用是简化我们的配置。比如我们保存和更新时需要两个转换配置(因为复制逻辑可能不一样,比如更新时:id,createTime的属性是不能改变的),但是其他属性的赋值逻辑是一致的,那么我们就可以通过自定义注解的方式来简化这个过程。我们来看一个列子:
@Common
UserVO toSave(User user);
@Mapping(target = "age", ignore = true)
@Common
void toUpdate(User user, @MappingTarget UserVO userVO);
@Retention(RetentionPolicy.CLASS)
@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "extend", target = ".")
public @interface Common {}
从测试可以看出来和配置可以得出结果:我们自定义的注解被 toSave() 和 toUpdate()继承了,数据符合我们的预期。
自定义表达式
自定义表达式是MapStruct提供的更高级和强大的功能。其可以支持你更灵活的去生成你属性的值。
自定义常量或默认值
- @Mapping:defaultValue 当源的属性值为null的值,给目标属性赋默认值。
- @Mapping:constant 直接给目标属性赋值,当使用constant时,无需使用source标签。
- @Mapping:ignore 可以配置那个属性被忽略,这个在为某些拷贝对象时某些属性不需要赋值时挺好用的。
自定义表达式
- @Mapping:defaultExpression 当源的属性值为null的值,调用一个表达式来计算一个值给目标属性。
- @Mapping:expression 给某个属性赋值表达式计算的值,同样当使用expression时,无需使用source标签。
我们来举几个列子:
- 给person对象的id赋值一个随机UUID。
@Mapping(expression = "java( UUID.randomUUID().toString() )",
target = "id")
PersonDto personToDto(Person person);
- 导入spring容器中的bean,并调用方法获取值并给目标属性赋值。
@Mapper(componentModel = "spring")
public abstract class SimpleMapper {
@Autowired
protected SimpleService simpleService;
@Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")
public abstract SimpleTarget sourceToTarget(SimpleSource source);
}
表达式非常灵活,大家可以自行多多研究,如果不确定映射关系是怎么样的时候,可以反编译代码查看。
最后:如果大家觉得有用,请点赞关注和收藏。你的鼓励是我更新的最大动力。
转载自:https://juejin.cn/post/7225555658007887927