一、MapStruct
开发中,我们经常需要将PO转DTO、DTO转PO等一些实体间的转换。比较出名的有BeanUtil 和ModelMapper等,它们使用简单,但是在稍显复杂的业务场景下力不从心。MapStruct这个插件可以用来处理domin实体类与model类的属性映射,可配置性强。只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。MapStruct官网地址: http://mapstruct.org/
二、MapStruct优势
为什么推荐这个框架呢,下面说说原因和他的优势
-
原因一: 很多项目大量映射通过手动get、set,这种写法非常繁琐无味,而且没有技术含量。甚至中间还牵涉了很多类型转换,嵌套之类的繁琐操作,非常的愚蠢。
-
原因二: 有人说apache的BeanUtil. copyProperties可以实现,但是性能差而且容易出异常,很多规范严禁使用这种途径。以下是对几种对象映射框架的对比,大多数情况下MapStruct性能最高。原理类似于lombok,MapStruct都是在编译期进行实现,而且基于Getter、Setter,,没有 使用反射所以-般不存在运行时性能问题。
-
原因三: 方便的映射操作,对我们平台统一代码有极其重大的意义。简化一套代码真正兼容多个平台的代码实现。使得开发人员多年对于多个联网平台统一代码的幻想又进了一步。
-
原因四: 在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。MapStruct 就是这样的一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。
-
原因五: 对于包装类是自动拆箱封箱操作的,并且是线程安全的。MapStruct不单单有这些功能,还有其他一些复杂的功能:设置转换默认值和常量。当目标值是null时我们可以设置其默认值。
三、MapStruct使用
3.1 单个对象转换
3.1.1 新建maven项目,引入依赖MapStruct、Lombok
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alanchen</groupId>
<artifactId>mapstruct</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<!-- lombok要与mapstruct版本匹配,用同一时间的版本,不然会出现各种问题 -->
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.6</org.projectlombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- 要与mapstruct版本匹配,用同一时间的版本,不然会出现各种问题 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.1.2 编写代码
entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String sex;
}
vo
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserVO {
private Integer id;
/**
* User的属性是name
*/
private String userName;
private Integer age;
private String sex;
}
convert
import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
* 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
*/
@Mapper
public interface UserConvert {
/**
* 获取该类自动生成的实现类的实例
* 接口中的属性都是 public static final 的 方法都是public abstract的
*/
UserConvert instance = Mappers.getMapper(UserConvert.class);
/**
* 这个方法就是用于实现对象属性复制的方法
*
* @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
*
* @param user 这个参数就是源对象,也就是需要被复制的对象
* @return 返回的是目标对象,就是最终的结果对象
*/
@Mappings({@Mapping(source = "name",target = "userName")})
UserVO toVO(User user);
@Mappings({@Mapping(source = "userName",target = "name")})
User toEntity(UserVO userVO);
}
@Mapper
只有在接口加上这个注解, MapStruct 才会去实现该接口
@Mapper
里有个 componentModel
属性,主要是指定实现类的类型,一般用到两个:
-
default: 默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象
-
spring: 在接口的实现类上自动添加注解
@Component
,可通过@Autowired
方式注入
@Mapping
:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
测试Client
public class Client {
public static void main(String[] args) {
poToVO();
System.out.println();
voTOPo();
}
private static void poToVO(){
User user = User.builder()
.id(1)
.name("AlanChen")
.age(18)
.sex("1")
.build();
System.out.println("user:"+user);
UserVO userVO = UserConvert.instance.toVO(user);
System.out.println("userVO:"+userVO);
}
private static void voTOPo(){
UserVO userVO = UserVO.builder()
.id(1)
.userName("AlanChen")
.age(18)
.sex("1")
.build();
System.out.println("userVO:"+userVO);
User user = UserConvert.instance.toEntity(userVO);
System.out.println("user:"+user);
}
}
运行结果
user:User(id=1, name=AlanChen, age=18, sex=1)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1)
user:User(id=1, name=AlanChen, age=18, sex=1)
启动运行,会在target目录下自动生成转换实现类
实现类3.1.3 跳坑
一开始没有注意MapStruct与Lombok匹配的问题,导出出现了一些问题,遇到的问题有以下两个
问题一:MapStruct生成的实现类缺失Entity转VO的具体的实现
没有设置属性问题二:出现java: No property named “XXX“ exists in source parameter(s). Did you mean “null“
MapStruct与Lombok的版本怎么匹配,我也不太清楚,我尝试着采用MapStruct、Lombok同一时期的版本,如mapstruct V1.3.0.Final
和 projectlombok V1.18.6
都是2019年二月份的版本,问题得到解决。
<!-- lombok要与mapstruct版本匹配,用同一时间的版本,不然会出现各种问题 -->
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.6</org.projectlombok.version>
问题三:如果对象有继承其他父类,转换也会失败
3.2 对象转换返回List
转化 List<> 集合时必须有 实体转化,因为在实现中,List 转换是 for循环调用 实体转化的。所以当属性名不对应时,应该在 实体转化进行 @Mappings 的属性名映射配置,然后list的转换也会继承这和属性的映射。
@Mapper
public interface UserConvert {
/**
* 获取该类自动生成的实现类的实例
* 接口中的属性都是 public static final 的 方法都是public abstract的
*/
UserConvert instance = Mappers.getMapper(UserConvert.class);
/**
* 转换成List
* @param users
* @return
*/
List<UserVO> toVOList(List<User> users);
}
完整代码为
import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
* 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
*/
@Mapper
public interface UserConvert {
/**
* 获取该类自动生成的实现类的实例
* 接口中的属性都是 public static final 的 方法都是public abstract的
*/
UserConvert instance = Mappers.getMapper(UserConvert.class);
/**
* 这个方法就是用于实现对象属性复制的方法
*
* @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
*
* @param user 这个参数就是源对象,也就是需要被复制的对象
* @return 返回的是目标对象,就是最终的结果对象
*/
@Mappings({@Mapping(source = "name",target = "userName")})
UserVO toVO(User user);
@Mappings({@Mapping(source = "userName",target = "name")})
User toEntity(UserVO userVO);
/**
* 转换成List
* @param users
* @return
*/
List<UserVO> toVOList(List<User> users);
}
测试Client
public class Client {
public static void main(String[] args) {
toList();
}
private static void toList(){
List<User> userList = new ArrayList<User>();
User user1 = User.builder()
.id(1)
.name("AlanChen")
.age(18)
.sex("1")
.build();
userList.add(user1);
User user2 = User.builder()
.id(2)
.name("AlanChen2")
.age(20)
.sex("0")
.build();
userList.add(user2);
List<UserVO> userVOList = UserConvert.instance.toVOList(userList);
System.out.println("userVOList:"+userVOList);
}
}
运行结果
userVOList:[UserVO(id=1, userName=AlanChen, age=18, sex=1), UserVO(id=2, userName=AlanChen2, age=20, sex=0)]
3.3 属性类型不同,自定义转换类
现在在User里加一个是否停用的属性private boolean stop;
为boolean
类型 ,但UserVO里是否停用的属性private String stop;
为String类型,二者属性不同,我们需要自己写一个转换类
public class UserTransform {
public String booleanToString(boolean value){
if(value){
return "停用";
}
return "未停用";
}
public boolean strToBoolean(String str){
if ("停用".equals(str)) {
return true;
}
return false;
}
}
UserConvert
加注解参数
@Mapper(uses = UserTransform.class)
import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
* 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
*/
@Mapper(uses = UserTransform.class)
public interface UserConvert {
/**
* 获取该类自动生成的实现类的实例
* 接口中的属性都是 public static final 的 方法都是public abstract的
*/
UserConvert instance = Mappers.getMapper(UserConvert.class);
/**
* 这个方法就是用于实现对象属性复制的方法
*
* @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
*
* @param user 这个参数就是源对象,也就是需要被复制的对象
* @return 返回的是目标对象,就是最终的结果对象
*/
@Mappings({@Mapping(source = "name",target = "userName")})
UserVO toVO(User user);
@Mappings({@Mapping(source = "userName",target = "name")})
User toEntity(UserVO userVO);
/**
* 转换成List
* @param users
* @return
*/
List<UserVO> toVOList(List<User> users);
}
Client测试代码
public class Client {
public static void main(String[] args) {
poToVO();
System.out.println();
voTOPo();
}
private static void poToVO(){
User user = User.builder()
.id(1)
.name("AlanChen")
.age(18)
.sex("1")
.stop(false)
.build();
System.out.println("user:"+user);
UserVO userVO = UserConvert.instance.toVO(user);
System.out.println("userVO:"+userVO);
}
private static void voTOPo(){
UserVO userVO = UserVO.builder()
.id(1)
.userName("AlanChen")
.age(18)
.sex("1")
.stop("停用")
.build();
System.out.println("userVO:"+userVO);
User user = UserConvert.instance.toEntity(userVO);
System.out.println("user:"+user);
}
}
运行结果
user:User(id=1, name=AlanChen, age=18, sex=1, stop=false)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=未停用)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=停用)
user:User(id=1, name=AlanChen, age=18, sex=1, stop=true)
3.4 dateFormat配置日期格式
在User类里继续加一个生日字段private Date birthday;
,UserVO类里加生日字段private String birthday;
在UserConvert里指定dateFormat
import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
* 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
*/
@Mapper(uses = UserTransform.class)
public interface UserConvert {
/**
* 获取该类自动生成的实现类的实例
* 接口中的属性都是 public static final 的 方法都是public abstract的
*/
UserConvert instance = Mappers.getMapper(UserConvert.class);
/**
* 这个方法就是用于实现对象属性复制的方法
*
* @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
*
* @param user 这个参数就是源对象,也就是需要被复制的对象
* @return 返回的是目标对象,就是最终的结果对象
*/
@Mappings({@Mapping(source = "name",target = "userName"),
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")})
UserVO toVO(User user);
@Mappings({@Mapping(source = "userName",target = "name"),
@Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")})
User toEntity(UserVO userVO);
/**
* 转换成List
* @param users
* @return
*/
List<UserVO> toVOList(List<User> users);
}
Client测试类
public class Client {
public static void main(String[] args) {
poToVO();
System.out.println();
voTOPo();
}
private static void poToVO(){
User user = User.builder()
.id(1)
.name("AlanChen")
.age(18)
.sex("1")
.stop(false)
.birthday(new Date())
.build();
System.out.println("user:"+user);
UserVO userVO = UserConvert.instance.toVO(user);
System.out.println("userVO:"+userVO);
}
private static void voTOPo(){
UserVO userVO = UserVO.builder()
.id(1)
.userName("AlanChen")
.age(18)
.sex("1")
.stop("停用")
.birthday("1990-05-20")
.build();
System.out.println("userVO:"+userVO);
User user = UserConvert.instance.toEntity(userVO);
System.out.println("user:"+user);
}
}
运行结果
user:User(id=1, name=AlanChen, age=18, sex=1, stop=false, birthday=Mon Nov 22 19:02:44 CST 2021)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=未停用, birthday=2021-11-22)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=停用, birthday=1990-05-20)
user:User(id=1, name=AlanChen, age=18, sex=1, stop=true, birthday=Sun May 20 00:00:00 CDT 1990)
3.5 ignore
ignore: 忽略这个字段
3.6 多对一映射
MapStruct 可以将几种类型的对象映射为另外一种类型,比如将多个entity对象转换为VO。例如:两个entity对象 Item 和 Sku,一个VO对象SkuVO
entity
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Item {
private Long id;
private String title;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Sku {
private String code;
private Integer price;
}
convert
@Mapper
public interface ItemConvert {
ItemConvert instance = Mappers.getMapper(ItemConvert.class);
@Mappings({
@Mapping(source = "sku.id",target = "skuId"),
@Mapping(source = "sku.code",target = "skuCode"),
@Mapping(source = "sku.price",target = "skuPrice"),
@Mapping(source = "item.id",target = "itemId"),
@Mapping(source = "item.title",target = "itemName")
})
SkuVO toVO(Item item, Sku sku);
}
网友评论