【实体转换】mapstruct详解
大家好,我是jstart千语。今天来给大家讲讲在项目中经常可以使用得到的一个“工具”,就是mapstruct。
一、工具介绍
这个工具有些类似于spring提供的BeanUtils.copyProperties()用于对象转化。而mapstruct是通过生成高效的、类型安全的映射代码来帮助开发人员在不同的数据对象之间进行转换。但他们的实现方式大有不同,从而他们从性能方面也会有差异。
总得来说,如果项目中使用到的实体转换场景不多,那么使用BeanUtils就可以了,但如果项目中需要大量使用到实体转换,那么使用mapstruct在提高性能方面就是一个很好的选择。
二、引入依赖
一般来说引入两个依赖,但是如果你用到了lombok依赖,一定要将lombok依赖放到mapstruct依赖前面。
<!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency> <!-- mapstruct --><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.4.2.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.4.2.Final</version></dependency>
三、实现方法
mapstruct的实现方法有两种方式:
- 依靠spring的依赖注入实现
- 通过使用mapstruct静态工厂来实现
区别:
spring依赖注入 | 更符合spring的代码风格, 支持自动注入其他 Bean。 |
mapstruct静态工厂 | 简单直接,实例生命周期需手动管理 |
两种在性能上没有什么区别,我更喜欢用静态工厂这种方法,因为它足够简单,只需要一行代码就可以搞定。
1、spring依赖注入方式
定义Mapper接口,使用@Mapper注解并指定componentModel = "spring",使其成为Spring Bean
// Mapper 接口(配置 componentModel = "spring")
@Mapper(componentModel = "spring")
public interface JudgeSubjectConverter {JudgeSubjectDTO toDto(JudgeSubject judgeSubject);
}// 使用方式(通过注入)
@Service
public class MyService {@Autowiredprivate JudgeSubjectConverter judgeSubjectConverter;public void process() {JudgeSubject judgeSubject = new JudgeSubject();JudgeSubjectDTO judgeSubjectDTO = judgeSubjectConverter.toDto(judgeSubject);}
}
2、mapstruct静态工厂
@Mapper
public interface JudgeSubjectConverter {JudgeSubjectConverter INSTANCE = Mappers.getMapper(JudgeSubjectConverter.class);JudgeSubjectDTO toDto(JudgeSubject judgeSubject);
}// 使用方式:直接通过类名调用
@Service
public void myService(){//...JudgeSubject judgeSubject = new JudgeSubject();JudgeSubjectDTO judgeSubjectDTO = JudgeSubjectConverter.INSTANCE.toDto(judgeSubject);//...}
3、共同功能
首先记得添加@Mapper注解时,导入的是mapstruct的包,不要导入了mybatis的包了。
(1)处理字段映射
默认的映射关系就是只有属性名相同才会映射转换,如果两个实体的属性名不同还需要进行转换的话就需要手动映射。
public class User {private String email;
}public class UserDTO {private String emailAddress;
}// Mapper 接口
@Mapper
public interface UserMapper {@Mapping(source = "email", target = "emailAddress")UserDTO toDto(User user);
}
(2)忽略字段
public class User {private String password;
}public class UserDTO {private String password; // 不希望映射此字段
}// Mapper 接口
@Mapper
public interface UserMapper {@Mapping(target = "password", ignore = true)UserDTO toDto(User user);
}
(3)格式化日期
public class User {private Date createTime;
}public class UserDTO {private String createTime;
}// Mapper 接口
@Mapper
public interface UserMapper {@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")UserDTO toDto(User user);
}
(4)多源对象映射
public class Order {private String orderId;
}public class User {private String userId;
}public class OrderDTO {private String orderId;private String userId;
}// Mapper 接口
@Mapper
public interface OrderMapper {@Mapping(source = "order.orderId", target = "orderId")@Mapping(source = "user.userId", target = "userId")OrderDTO toDto(Order order, User user);
}
例2:
/**
*题目信息表
*/
@Data
public class SubjectInfo implements Serializable {private static final long serialVersionUID = -71318372165220898L;/*** 主键*/private Long id;/*** 题目名称*/private String subjectName;}
---------------------------------------------------------------------------------------/**
*题目DTO
*/
@Data
public class SubjectOptionBO implements Serializable {/*** 题目答案*/private String subjectAnswer;/*** 题目选项的答案内容*/private List<SubjectAnswerBO> optionList;}
---------------------------------------------------------------------------------------
/**
*题目BO
*/
@Data
public class SubjectInfoBO extends PageInfo implements Serializable {//主键private Long id;//题目名称private String subjectName;//题目答案private String subjectAnswer;//答案选项private List<SubjectAnswerBO> optionList;}
---------------------------------------------------------------------------------------/**
* 多源类型转换
*/
@Mapper
public interface SubjectInfoConverter {SubjectInfoBO convertOptionAndInfoToBo(SubjectOptionBO subjectOptionBO,SubjectInfo subjectInfo);
}
(5)设置默认值
当源字段为 null 时,使用默认值:
@Mapping(target = "description", source = "desc", defaultValue = "暂无描述")
ProductDTO toDto(Product product);
(6)设置常量值
直接为目标字段赋固定值:
@Mapping(target = "type", constant = "VIP")
UserDTO toDto(User user);
四、与BeanUtils.copyProperties()比较
他们两个都可以进行对象转换,最主要的区别就是性能上了。
(1)Spring 的 BeanUtils.copyProperties()
底层是通过 Java 的 反射机制 实现的:
- 运行时获取源对象和目标对象的 getter/setter 方法;
- 检查方法签名是否匹配;
- 动态调用 setXxx() 设置属性值。
性能影响:
-
反射调用慢:反射比直接方法调用慢得多,因为它要解析类结构、做安全检查、动态调用方法;
-
运行期做类型判断和匹配:每次调用都要做这些检查;
-
不可内联优化:JVM 对反射方法优化很有限,无法像普通方法调用一样 JIT 编译优化。
(2)MapStruct 是编译期生成代码(纯 Java)
MapStruct 在你编译代码的时候,就自动生成了普通 Java 类和方法来做对象转换,不使用反射。
- 这些方法就是标准的 obj.setXxx(obj.getYyy());
- 完全等同于你自己写一堆 setter 调用,只不过 MapStruct 自动帮你写好了。
性能优势:
-
没有反射开销;
-
纯 Java 调用,JVM 可以 JIT 优化、内联优化;
-
内存更友好,因为不需要动态创建 Method 对象;
-
执行速度接近你手写转换方法的极限。
(3)MapStruct 字节码展示
编译完后: