前言
MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。
您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。
与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。
与动态映射框架相比,MapStruct具有以下优点:
- 通过使用普通方法调用(settter/getter)而不是反射来快速执行
- 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
- 如果有如下问题,编译时会抛出异常
3.1 映射不完整(并非所有目标属性都被映射)
3.2 映射不正确(找不到正确的映射方法或类型转换) - 可以通过freemarker定制化开发
准备:maven依赖
<!--mapStruct依赖 高性能对象映射-->
<!--mapstruct核心-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.0.Beta1</version>
</dependency>
<!--mapstruct编译-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.0.Beta1</version>
</dependency>
Lombok依赖:(版本最好在1.16.16以上,否则会出现问题)通常是和lombok一起使用
的
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>${lombok.version}</version>
// 版本号 1.18.12
</dependency>
可以下载 mapstruct support 插件
项目中使用
实体类
package com.erp.platform.service.report.impl.mapstruct;
/**
* @Package com.erp.platform.common.params.sys.payrollReport.ecsReport
* @Author guang
* @Description 描述:
* @Date 2023/1/31 21:06
*/
public class User {
private String name;
private String age;
@Convert(converter = SubjectEnum.Convert.class)
private SubjectEnum subject;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
package com.erp.platform.service.report.impl.mapstruct;
import lombok.Data;
/**
* @Package com.erp.platform.service.report.impl.mapstruct
* @Author guang
* @Description 描述:
* @Date 2023/2/4 21:05
*/
@Data
public class UserVO {
private String name;
private String nianLing;
private SubjectEnum subject;
private String createStamp;
}
package com.erp.platform.service.report.impl.mapstruct;
import lombok.Data;
/**
* @Package com.erp.platform.service.report.impl.mapstruct
* @Author guang
* @Description 描述:
* @Date 2023/2/4 22:57
*/
@Data
public class UserUpdateVO {
private String name;
private String age;
private SubjectEnum subject;
private String createStamp;
}
枚举类
package com.erp.platform.service.report.impl.mapstruct;
/**
* @Package com.erp.platform.service.report.impl.mapstruct
* @Author guang
* @Description 描述:
* @Date 2023/2/1 22:00
*/
public enum SubjectEnum implements DbCodeField{
English("1","英语"),
mathematics("2","数学"),
language("3","语文");
private String code;
private String name;
SubjectEnum(String code, String name){
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public static class Convert extends DbCodeFieldConverter<SubjectEnum> {}
}
枚举类继承类
package com.erp.platform.service.report.impl.mapstruct;
import lombok.SneakyThrows;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Objects;
/**
* @Package com.erp.platform.service.report.impl.mapstruct
* @Author guang
* @Description 描述:
* @Date 2023/2/1 22:10
*/
@Converter
public class DbCodeFieldConverter<E extends DbCodeField> implements AttributeConverter<E, String> {
/**
* E的class对象
*/
private final Class<E> eClazz;
/**
* 枚举类的 values() 方法
*/
private final Method valuesMethod;
public DbCodeFieldConverter() {
this.eClazz = (Class<E>) ((ParameterizedType) this.getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
try {
this.valuesMethod = this.eClazz.getMethod("values");
} catch (NoSuchMethodException e) {
throw new RuntimeException("非枚举类无法使用Converter", e);
}
}
@Override
public String convertToDatabaseColumn(E attribute) {
if (Objects.nonNull(attribute)) {
return attribute.getCode();
}
return null;
}
@SneakyThrow
@Override
public E convertToEntityAttribute(String dbData) {
if (Objects.nonNull(dbData)) {
E[] elements = getEnumElements();
return Arrays.stream(elements).filter(e -> e.getCode().equals(dbData))
.findFirst().orElseThrow(() -> new BizException(CommonResponseCode.FAILURE.getCode()),
MessageFormat.format("未找到与数据库中代码值匹配的枚举元素,代码值:[f03],枚举类: []",
dbData, this.eClazz));
}
return null;
}
@SneakyThrows
private E[] getEnumElements(){
return (E[]) valuesMethod.invoke( null);
}
}
package com.erp.platform.service.report.impl.mapstruct;
/**
* 数据库表中代码类字段安现该按口以使用Converter。
* 数据字典要求所有代码类字度《枚举类》在数据库中以varchar或bpchac(定长字申)类型保存,对应java string
* 在使用3PA的情况下,将Entity中枚举类型字段映射到数据库,还要保留诸如"01","02"这类任意映射
* 最适合方式是自已号Convertor
*/
public interface DbCodeField {
/**
* 获取枚举中代表数据库代码值的字段的值
* 如果那个字段就叫code,正好
*
* @return 代码值
*/
String getCode();
}
转换类
package com.erp.platform.service.report.impl.mapstruct;
// import org.mapstruct.Mapper;
// import org.mapstruct.Mapping;
// import org.mapstruct.Mappings;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
/**
* @Package com.erp.platform.service.report.impl.mapstruct
* @Author guang
* @Description 描述:
* @Date 2023/2/4 20:40
*/
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
/**
* 用户 DO 转 VO
* 2023/2/4
* @since 1.0.0
*/
@Mappings(value = {
// 设置时间格式
@Mapping(source = "createStamp", target = "createStamp", dateFormat = "yyyy-MM-dd"),
// 设置属性名不一样的两属性
@Mapping(source = "age", target = "nianLing"),
// 运用java设置某值
@Mapping(target = "subject", expression = "java(user.getSubject().getCode())"),
// 忽略某属性
@Mapping(target = "name", ignore = true),
})
UserVO doToVO (User user);
@Mappings(value = {
// code转枚举
@Mapping(target = "subject", expression = "java(new com.erp.platform.service.report.impl.mapstruct" +
".SubjectEnum.Convert().convertToEntityAttribute(userVO.getSubject()))"),
// 忽略某属性
@Mapping(target = "name", ignore = true),
})
User voToDO (UserVO userVO);
// 修改操作时null值的不做保存,user是查出来的值
@BeanMapping(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
User updateVOToDO (UserUpdateVO updateVO, @MappingTarget User user);
}