- 0. 相关依赖:
- 1. 实体类信息:
- 2. BeanUtil方法转换:
- 2.1. 实体类转实体类(copyProperties):
- 2.2. 实体类集合转实体类集合(copyToList):
- 2.3. 实体类集合转Map(beanToMap):
- 2.4. 嵌套实体类转换:
- 3. MapStruct方法转换:
- 3.1. MapStruct的cover接口:
- 3.1. MapStruct转换使用:
个人常用的是hutool的BeanUtil.copyProperties以及MapStruct,这里也主要介绍一下常用用法
0. 相关依赖:
<!-- hutool工具类的依赖 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.12</version>
</dependency>
<!-- jdk8以下就使用mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
</dependency>
<!-- lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
1. 实体类信息:
- 主要区别:PO和VO的name不一样,然后PO的字段多一些
2. BeanUtil方法转换:
2.1. 实体类转实体类(copyProperties):
po为原实体类,vo为转换后的实体类
- 默认字段匹配上的字段都会转换,所以vo没有userName/name字段,因为po的userName和vo的name对不上
- vo1转换的时候设置了忽略age字段转换,所以vo1不会转换赋值age字段
- vo2通过CopyOptions进行了字段映射,将userName的值映射到name上,所以vo2的name是有值的
@Test
public void po2vo() {
UserInfoPO po = UserInfoPO.builder().userId(1L).userName("zhangsan").age("18").build();
System.out.println(JSON.toJSONString(po)); // {"age":"18","userId":1,"userName":"zhangsan"}
UserInfoVO vo = BeanUtil.copyProperties(po, UserInfoVO.class);
System.out.println(JSON.toJSONString(vo)); // {"age":"18","userId":1}
UserInfoVO vo1 = BeanUtil.copyProperties(po, UserInfoVO.class, "age");
System.out.println(JSON.toJSONString(vo1)); // {"userId":1}
UserInfoVO vo2 = UserInfoVO.builder().build();
HashMap<String, String> map = Maps.newHashMap();
map.put("userName", "name");
CopyOptions copyOptions = CopyOptions.create().setFieldMapping(map);
BeanUtil.copyProperties(po, vo2, copyOptions);
System.out.println(JSON.toJSONString(vo2)); // {"age":"18","name":"zhangsan","userId":1}
}
2.2. 实体类集合转实体类集合(copyToList):
list为原实体类集合,vos为转换后的实体类集合
- 默认字段匹配上的字段都会转换,所以vos没有userName/name字段,因为po的userName和vo的name对不上
- vos1转换的时候设置了忽略age字段转换,所以vos1不会转换赋值age字段
- vos2通过CopyOptions进行了字段映射,将userName的值映射到name上,所以vos2的name是有值的
@Test
public void pos2vos() {
UserInfoPO po = UserInfoPO.builder().userId(1L).userName("zhangsan").age("18").build();
ArrayList<UserInfoPO> list = Lists.newArrayList(po);
System.out.println(JSON.toJSONString(list)); // [{"age":"18","userId":1,"userName":"zhangsan"}]
List<UserInfoVO> vos = BeanUtil.copyToList(list, UserInfoVO.class);
System.out.println(JSON.toJSONString(vos)); // [{"age":"18","userId":1}]
CopyOptions copyOptions = CopyOptions.create().setIgnoreProperties("age");
List<UserInfoVO> vos1 = BeanUtil.copyToList(list, UserInfoVO.class, copyOptions);
System.out.println(JSON.toJSONString(vos1)); // [{"userId":1}]
HashMap<String, String> map = Maps.newHashMap();
map.put("userName", "name");
CopyOptions copyOption1 = CopyOptions.create().setFieldMapping(map);
List<UserInfoVO> vos2 = BeanUtil.copyToList(list, UserInfoVO.class, copyOption1);
System.out.println(JSON.toJSONString(vos2)); // [{"age":"18","name":"zhangsan","userId":1}]
}
2.3. 实体类集合转Map(beanToMap):
直接调方法转即可,还可以设置是否进行key的驼峰转换
@Test
public void po2map() {
UserInfoPO po = UserInfoPO.builder().userId(1L).userName("zhangsan").age("18").build();
System.out.println(JSON.toJSONString(po)); // {"age":"18","userId":1,"userName":"zhangsan"}
Map<String, Object> map = BeanUtil.beanToMap(po);
System.out.println(JSON.toJSONString(map)); // {"age":"18","userId":1,"userName":"zhangsan"}
Map<String, Object> map1 = BeanUtil.beanToMap(po, true, false);
System.out.println(JSON.toJSONString(map1)); // {"user_id":1,"user_name":"zhangsan","age":"18"}
}
2.4. 嵌套实体类转换:
UserInfoPO嵌套了JobPO,po为原实体类,vo为转换后的实体类
- 默认字段匹配上的字段都会转换,所以vo的name没有值,嵌套的job的jname也没有值,因为匹配不上
- 要像嵌套的实体类也能映射上,需要像如下vo2这样copyProperties转换两次
@Test
public void po2voNested() {
JobPO jobPO = JobPO.builder().jobName("java").jobId(1L).build();
UserInfoPO po = UserInfoPO.builder().userId(1L).userName("lisi").job(jobPO).build();
System.out.println(JSON.toJSONString(po)); // {"job":{"jobId":1,"jobName":"java"},"userId":1,"userName":"lisi"}
UserInfoVO vo = BeanUtil.copyProperties(po, UserInfoVO.class);
System.out.println(JSON.toJSONString(vo)); // {"job":{"jobId":1},"userId":1}
UserInfoVO vo1 = BeanUtil.copyProperties(po, UserInfoVO.class, "age");
System.out.println(JSON.toJSONString(vo1)); // {"job":{"jobId":1},"userId":1}
UserInfoVO vo2 = UserInfoVO.builder().build();
HashMap<String, String> map = Maps.newHashMap();
map.put("userName", "name");
map.put("jobName", "jName");
CopyOptions copyOptions = CopyOptions.create().setFieldMapping(map);
BeanUtil.copyProperties(po, vo2, copyOptions);
System.out.println(JSON.toJSONString(vo2)); // {"job":{"jobId":1},"name":"lisi","userId":1}
BeanUtil.copyProperties(po.getJob(), vo2.getJob(), copyOptions);
System.out.println(JSON.toJSONString(vo2)); // {"job":{"jName":"java","jobId":1},"name":"lisi","userId":1}
}
- 如果像一些简单的bean或者bean集合,不一致的字段比较少,用BeanUtil还是可以接受的
- 但像字段比较多或者嵌套实体类或者不一致的字段比较多,首先需要自定义CopyOptions,代码比较不雅观
- BeanUtil转换过程是基于反射的,性能也会有点影响
3. MapStruct方法转换:
MapStruct网上也有很多介绍了,它不是基于反射的,在编译时就帮我们set值,所以相对高效一点
3.1. MapStruct的cover接口:
【说明】:
- @Mapper是mapstruct的注解
- 通过Mappers.getMapper定义一个转换对象
- 多个字段名称不一致,可以使用@Mappings进行映射关联,仅有一个可以直接使用@Mapping
- 要想忽略某些字段转换,定义@Mapping的属性ignore = true即可
- 同一个接口文件,存在多个相同入参对应相同返回值类型,如果不做处理,会报下方说明第二个异常
- 处理时可以使用@IterableMapping来指定编译用哪个方法进行转换
- @AfterMapping可以对转换后的实体进行一些处理,比如将name转为大写;同样也存在@BeforeMapping
·
【异常说明】:
- 编译时报java Internal error in the mapping processor java.lang.NullPointerException,和idea有关,可以参考设置
- 允许时报MapStruct: Ambiguous mapping methods found for mapping collection element,可以参考修改
- 使用@AfterMapping时,可能存在与@Builder冲突,导致方法不执行情况,可以参考链接
@Mapper
public interface UserInfoCover {
UserInfoCover INSTANCE = Mappers.getMapper(UserInfoCover.class);
@Mappings({
@Mapping(source = "userName", target = "name")
})
UserInfoVO toConvertUserInfoVo(UserInfoPO po);
@Mapping(source = "jobName", target = "jName")
JobVO toConvertJobVo(JobPO po);
@IterableMapping(qualifiedByName = "toConvertUserInfoVoWithNoAge")
List<UserInfoVO> toConvertUserInfoVos(List<UserInfoPO> pos);
@Named("toConvertUserInfoVoWithNoAge")
@Mappings({
@Mapping(source = "userName", target = "name"),
@Mapping(source = "age", target = "age", ignore = true)
})
UserInfoVO toConvertUserInfoVoWithNoAge(UserInfoPO po);
@Mapping(source = "userName", target = "name")
UserInfoVO toConvertAfter(UserInfoPO po);
@AfterMapping
default void toConvertAfter(UserInfoPO po, @MappingTarget UserInfoVO.UserInfoVOBuilder vo) {
vo.name(StringUtils.toRootUpperCase(po.getUserName()));
}
}
3.1. MapStruct转换使用:
【说明】:
- UserInfoVO拥有嵌套实体类,所以编写上述接口文件时,不仅要编写UserInfoVO的转换方法,还需要编写JobVO的转换方法
- 同样,集合转换时不仅要编写toConvertUserInfoVos转集合的方法,还需要对应的实体类转换方法
- 总体像实体类转换和集合转换,MapStruct还是十分间接优雅的
@Test
public void po2voNested() {
JobPO jobPO = JobPO.builder().jobName("java").jobId(1L).build();
UserInfoPO po = UserInfoPO.builder().userId(1L).userName("lisi").job(jobPO).build();
System.out.println(JSON.toJSONString(po)); // {"job":{"jobId":1,"jobName":"java"},"userId":1,"userName":"lisi"}
UserInfoVO vo = UserInfoCover.INSTANCE.toConvertUserInfoVo(po);
System.out.println(JSON.toJSONString(vo)); // {"job":{"jName":"java","jobId":1},"name":"lisi","userId":1}
}
@Test
public void pos2vos() {
UserInfoPO po = UserInfoPO.builder().userId(1L).userName("zhangsan").age("18").build();
ArrayList<UserInfoPO> list = Lists.newArrayList(po);
System.out.println(JSON.toJSONString(list)); // [{"age":"18","userId":1,"userName":"zhangsan"}]
List<UserInfoVO> vos = UserInfoCover.INSTANCE.toConvertUserInfoVos(list);
System.out.println(JSON.toJSONString(vos)); // [{"name":"zhangsan","userId":1}]
}
@Test
public void po2voAfter() {
JobPO jobPO = JobPO.builder().jobName("java").jobId(1L).build();
UserInfoPO po = UserInfoPO.builder().userId(1L).userName("lisi").job(jobPO).build();
System.out.println(JSON.toJSONString(po)); // {"job":{"jobId":1,"jobName":"java"},"userId":1,"userName":"lisi"}
UserInfoVO userInfoVO = UserInfoCover.INSTANCE.toConvertAfter(po);
System.out.println(JSON.toJSONString(userInfoVO)); // {"job":{"jName":"java","jobId":1},"name":"LISI","userId":1}
}