最近发现了个很好玩的工具包org.mapstruct
,里面处理类型转换的功能总觉得很高大上。特此写一篇博客记录测试、使用心得。
平时的开发中,针对与数据库做数据交互
操作时,一般定义一个vo
或者pojo
类,在接收前端页面的参数信息时,会采取定义一个dto
类的形式。
但是在开发中,难免会碰见需要将 dto 类转换为对应的 vo 类
,达到和数据库数据交互的目的。
以前,我最喜欢采取自己手写get/set
的代码,手动将数据信息进行转换。稍微用得高大上点的就是采取BeanUtils.copyProperties(source,target)
。
但机缘巧合下,发现了更好用的工具类org.mapstruct:mapstruct
。
他能够让类型转换之间更简单快捷,其次还支持部分参数调用指定Java代码解析!
接下来就一起见证下他的神奇之处。
本次测试环境采取Springboot 2.1.4.RELEASE
和mapstruct-processor 1.3.0.Final
。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <dependencies> <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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
既然文章开头说到pojo
和dto
两种类型的转换操作,接下来就新建这两个类。
User.java
:
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
UserDto.java
:
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class UserDto { private Long id; private String name; private Integer age; private String email; }
一般的类型转换,参考下列测试类:
import cn.xj.StartApplication; import cn.xj.pojo.User; import cn.xj.pojo.UserDto; import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(classes = StartApplication.class) @Slf4j public class Test1 { UserDto userDto = null; @Before public void before(){ // 假定这是一个前端传递来得数据信息 userDto = new UserDto(1L,"xiangjiao dto",22,null); } @Test public void test1(){ User user = new User(); user.setId(userDto.getId()); user.setName(userDto.getName()); user.setAge(userDto.getAge()); user.setEmail(userDto.getEmail()); System.out.println(user); } }
采取一般的get/set
方式,手动
将指定的类中的数据转换至指定的类中
。
BeanUtils.copyProperties
也能实现类似上面的功能,如下所示:
UserDto userDto = null; @Before public void before(){ // 假定这是一个前端传递来得数据信息 userDto = new UserDto(1L,"xiangjiao dto",22,null); } @Test public void test2(){ User user = new User(); // 第一个参数表示:源数据对象 // 第二个参数表示:目标对象 BeanUtils.copyProperties(userDto,user); System.out.println(user); }
mapstruct
如果需要实现上面的功能,需要编写一个接口
,如下所示:
package cn.xj.interfaces; import cn.xj.pojo.User; import cn.xj.pojo.UserDto; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; @Mapper // 和 Mybatis的很像哦 public interface MapStructInf { /** * 获取该类自动生成的实现类的实例 * 接口中的属性都是 public static final 的 方法都是public abstract的 */ MapStructInf instances = Mappers.getMapper(MapStructInf.class); /** * 这个方法就是用于实现对象属性复制的方法 * * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性 * * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象 * @return 返回的是 目标对象,就是最终的结果对象 */ @Mappings({ @Mapping(source = "id",target = "id"), @Mapping(source = "name",target = "name"), @Mapping(source = "age",target = "age"), @Mapping(source = "email",target = "email") }) User tranceToUser(UserDto userDto); }
编写一个测试类,进行测试:
import cn.xj.StartApplication; import cn.xj.interfaces.MapStructInf; import cn.xj.pojo.User; import cn.xj.pojo.UserDto; import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(classes = StartApplication.class) @Slf4j public class MapstructTest { UserDto userDto = null; @Before public void before(){ // 假定这是一个前端传递来得数据信息 userDto = new UserDto(1L,"xiangjiao dto",22,null); } @Test public void test1(){ User user = MapStructInf.instances.tranceToUser(userDto); System.out.println(user); } }
初步一看,有些观众大姥爷可能会说:
写这么一大篇的
接口
,就为了转换一个类,吃多了吧!
上面只是简单的使用测试,接下来看点有意思的。
继续在指定的cn.xj.interfaces.MapStructInf
接口类中,定义一个转换集合的方法。如下所示:
import cn.xj.pojo.User; import cn.xj.pojo.UserDto; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; import java.util.List; @Mapper // 和 Mybatis的很像哦 public interface MapStructInf { /** * 获取该类自动生成的实现类的实例 * 接口中的属性都是 public static final 的 方法都是public abstract的 */ MapStructInf instances = Mappers.getMapper(MapStructInf.class); /** * 这个方法就是用于实现对象属性复制的方法 * * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性 * * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象 * @return 返回的是 目标对象,就是最终的结果对象 */ // @Mappings({ // @Mapping(source = "id",target = "ids"), // @Mapping(source = "name",target = "names"), // @Mapping(source = "age",target = "ages"), // @Mapping(source = "email",target = "emails") // }) // User tranceToUser(UserDto userDto); // 转换集合 List<User> tranceToUserList(List<UserDto> userDtoList); }
编写测试类进行测试:
import cn.xj.StartApplication; import cn.xj.interfaces.MapStructInf; import cn.xj.pojo.User; import cn.xj.pojo.UserDto; import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest(classes = StartApplication.class) @Slf4j public class MapstructTest { List<UserDto> userDtoList; @Before public void before(){ // 假定这是一个前端传递来得数据信息 userDtoList = Arrays.asList( new UserDto(1L,"xiangjiao dto",11,null), new UserDto(2L,"xiangjiao dto",22,null), new UserDto(3L,"xiangjiao dto",33,null), new UserDto(4L,"xiangjiao dto",44,null)); } @Test public void test2(){ List<User> users = MapStructInf.instances.tranceToUserList(userDtoList); users.forEach(e->{ System.out.println(e); }); } }
结果却是可行的。
【注意:】这里有个小细节!
如果两个类的属性名是一样的,可以执行成功!
如果两个类的属性名 不同,则数据转换失败!!
为了测试这个问题,则需要将User.java
类中的属性名和UserDto.java
类中的属性名区分开。如下所示:
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long ids; private String names; private Integer ages; private String emails; }
再次指定上面的test2
测试方法,控制台打印日志信息如下所示:
【疑问:】
当这种问题出现,如何解决呢?
只需要
增加一个配置方法!
import cn.xj.pojo.User; import cn.xj.pojo.UserDto; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; import java.util.List; @Mapper // 和 Mybatis的很像哦 public interface MapStructInf { /** * 获取该类自动生成的实现类的实例 * 接口中的属性都是 public static final 的 方法都是public abstract的 */ MapStructInf instances = Mappers.getMapper(MapStructInf.class); /** * 这个方法就是用于实现对象属性复制的方法 * * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性 * * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象 * @return 返回的是 目标对象,就是最终的结果对象 */ @Mappings({ @Mapping(source = "id",target = "ids"), @Mapping(source = "name",target = "names"), @Mapping(source = "age",target = "ages"), @Mapping(source = "email",target = "emails") }) User tranceToUser(UserDto userDto); // 转换集合 List<User> tranceToUserList(List<UserDto> userDtoList); }
再次执行test2()
,可以看到控制台打印日志如下所示:
【原因:】
当
两个类之间
的属性别名不一致
时,可以采取定义单个类转换的关系
,实现集合数据的转换!
除了上面的基本操作之外,mapstruct
还能支持转换时,采取Java代码方式转换。
比如:
定义一个性别枚举类,但是
User.java
类接收的是性别名称,UserDto.java
类中却传递的是性别编号
。
首先,定义一个枚举类,提供可以根据编号查询名称的函数
:
package cn.xj.pojo2; public enum SexEnum { man(1,"男"), woman(2,"女"); private Integer value; private String name; // 注意这里一定要是 static // expression 只能调用静态方法 public static String getValByName(Integer value){ String names = null; for (SexEnum sexEnum : values()){ Integer value1 = sexEnum.getValue(); if(value1.equals(value)){ names = sexEnum.getName(); break; } } return names; } SexEnum(Integer value, String name) { this.value = value; this.name = name; } public Integer getValue() { return value; } public void setValue(Integer value) { this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
再修改对应的User.java
类:
保证
User.java
类接收性别别名
。
package cn.xj.pojo2; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long ids; private String names; private Integer ages; private String emails; private String sex; // 性别别名 }
定义前端页面数据接收类UserDto.java
:
保证性别信息采取
编号接收
。
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class UserDto { private Long id; private String name; private Integer age; private String email; private Integer sexNum; // 性别编号 }
定义转换方式:
import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; @Mapper // 和 Mybatis的很像哦 public interface MapStructInf2 { /** * 获取该类自动生成的实现类的实例 * 接口中的属性都是 public static final 的 方法都是public abstract的 */ MapStructInf2 instances = Mappers.getMapper(MapStructInf2.class); /** * 这个方法就是用于实现对象属性复制的方法 * * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性 * * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象 * @return 返回的是 目标对象,就是最终的结果对象 */ @Mappings({ @Mapping(source = "id",target = "ids"), @Mapping(source = "name",target = "names"), @Mapping(source = "age",target = "ages"), @Mapping(source = "email",target = "emails"), @Mapping(target = "sex",expression = "java(cn.xj.pojo2.SexEnum.getValByName(userDto.getSexNum()))") }) User tranceToUser(UserDto userDto); }
执行后的结果如下所示:
org.mapstruct:mapstruct 包的使用
mapstruct 实体转换及List转换
gitee 仓库地址