MapStruct 简介
官网地址:MapStruct
官方示例:mapstruct-examples
关联:
1. 概述
是什么?
一个 Java 注释处理器来生成类型安全的 Bean 映射类。
为什么要用?
Mapstruct的性能远远高于BeanUtils。且BeanUtils.copyProperties()只能转换类中字段名字一样且类型一样的字段
2. 功能说明
2.1 默认映射规则
同类型,同名的属性会自动映射
自动类型转换
基本类型和包装类型会自动转换,
BigDecimal(等)、8种基本类型(及包装类型)和String
日期类型(java.time.LocalDateTime、java.util.Date等)和String
enum和String
java.sql.Timestamp和java.util.Date
2.2 指定某个属性的映射规则
source和target多余的属性对方没有,不会报错
source映射源属性,target目标映射属性,defaultValue默认值,若源字段为空,则目标字段就为此默认值。
numberFormat = "#.00" 数字格式化,保留两位小数
dateFormat = "yyyy-MM-dd HH:mm:ss" 日期格式化
ignore=true 不映射该属性
constant 标识字段值为常量
expression 为 target 属性赋予表达式的运算值,可以是默认值、一段代码,也可以是函数方法,还可以调用用@Mapper#imports 导入的类中的方法,当然也包括导入类的构造方法。
嵌套结构,子对象的属性完全一致时,target 可以用
.
指代defaultExpression 默认表达式,仅source值为null时生效
@Mapper(imports = {StringBOUtils.class})
public interface EntityMapper {
EntityMapper INSTANCE = Mappers.getMapper(EntityMapper.class );
// 定义映射方法,指定映射规则
@Mappings({
// 基本类型 --> String
@Mapping(source = "intValue", target = "intValueStr"),
// 基本类型包装类 --> 基本类型
@Mapping(source = "integerValue", target = "intValue"),
// LocalDateTime --> String
@Mapping(source = "localDateTimeValue", target = "localDateTimeValueStr"),
// BigDecimal --> String 保留两位小数
@Mapping(source = "bigDecimalValue", target = "formattedBigDecimal", numberFormat = "#.00"),
// Date --> String 指定时间格式 yyyy-MM-dd HH:mm:ss
@Mapping(source = "dateValue", target = "formattedDate", dateFormat = "yyyy-MM-dd HH:mm:ss"),
// 枚举类 --> String
@Mapping(source = "enumValue", target = "enumValueAsString"),
// Timestamp --> Date
@Mapping(source = "timestampValue", target = "timestampAsDate"),
// defaultValue 设置默认值,来源为null时,目标为默认值。
@Mapping(source = "stringValue", target = "stringValue",defaultValue = "Hello"),
// 目标值指定为常量
@Mapping(target = "constantStr",constant = "我是常量"),
// ignore = true 忽略属性,不拷贝
@Mapping(target = "ignoreStr",ignore = true),
// expression 表达式:常量
@Mapping(target = "expression_str",expression = "java(\"Jaws\")"),
// expression 表达式:一段代码
@Mapping(target = "expression_date",expression = "java(\"表达式 --> 时间:\" + new java.util.Date())"),
// expression 表达式:方法调用-当前类
@Mapping(target = "expression_method",expression = "java(EntityMapper.getLastRunTime(sourceEntity))"),
// expression 表达式:方法调用-外部工具类
@Mapping(target = "expression_util", expression = "java(StringBOUtils.toBOString(sourceEntity.getIgnore()))"),
// 嵌套-子对象属性完全一致,可以用.
@Mapping(target = ".", source = "baby"),
// 默认表达式,仅source值为null时生效
@Mapping(target="id", source="sourceId", defaultExpression = "java(\"UUID\" )")
})
TargetEntity mapToTarget(SourceEntity sourceEntity);
static String getLastRunTime(SourceEntity sourceEntity){
return "lastRunTime --> " + sourceEntity.getDateValue();
}
}
2.3 @MappingTarget、@BeforeMapping、@AfterMapping、@Context
@MappingTarget 注解的参数获得的是目标实例
@MappingTarget 表示传来的carVO对象是已经赋值过的
@BeforeMapping 前置处理方法
@AfterMapping 后置处理方法
@AfterMapping
public void dat2voAfter(CarDTO carDTO,@MappingTarget CarVO carVO){
List<PartDTO> partDTOS = carDTO.getPartDTOS();
//判断集合是否为空,给carVo设置值
boolean hasPart = partDTOS != null && !partDTOS.isEmpty();
carVO.setHasPart(hasPart);
}
2.4 相似结构,且同属性名转换
示例详见 【3.6 mapstruct-field-mapping】
List<CarVO> dtos2vos(List<CarDTO> carDTOS);
2.5 @BeanMappring 指定映射属性
配置忽略mapstruct的默认映射行为,只映射了Mapping的属性
@BeanMapping(ignoreByDefault = true)
@Mapping(source = "id",target = "id")
@Mapping(source = "brand",target = "brandName")
VehicleVO carDTO2vehicleVO(CarDTO carDTO);
2.6 @InheritConfiguration 配置复用
@BeanMapping(ignoreByDefault = true)
@Mapping(source = "id",target = "id")
@Mapping(source = "brand",target = "brandName")
VehicleVO carDTO2vehicleVO(CarDTO carDTO);
@InheritConfiguration
/*@BeanMapping(ignoreByDefault = true)
@Mapping(source = "id",target = "id")
@Mapping(source = "brand",target = "brandName")*/
@Mapping(target = "id",ignore = true)
VehicleVO updateVehicleVO(CarDTO carDTO,@MappingTarget VehicleVO vehicleVO);
2.7 @InheritInverseConfiguration 反向映射
反向继承 name指定用哪个方法
只继承
@Mapping
注解,不继承@BeanMapring
注解
@BeanMapping(ignoreByDefault = true)
@Mapping(source = "id",target = "id")
@Mapping(source = "brand",target = "brandName")
//VehicleVO(id=330, price=null, brandName=品牌)
public abstract VehicleVO carDTO2vehicleVO(CarDTO carDTO);
@InheritInverseConfiguration(name = "carDTO2vehicleVO")
@BeanMapping(ignoreByDefault = true)
public abstract CarDTO vehicleVO2carDTO(VehicleVO vehicleVO);
2.8 与Spring整合
转换生成的对象会被Spring容器所管理
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public abstract class CarConvert {
//获取对象INSTANCE并使用
public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);
//转换方法
public abstract CarVO dto2vo(CarDTO carDTO);
}
3. 示例实践
此处,摘选自官方示例项目 mapstruct-examples 的README文档
个人实践示例代码详见:SpringBoot-Labs-Junw 中的 jLab-2-MapStruct-jdk17 jLab-2-MapStruct-jdk8
3.1 兼容性异常
3.1.1 mapstruct-lombok
展示如何将 MapStruct 与 Lombok 一起使用
注:高版本的Lombok 和 MapStruct 存在兼容性问题,解决方案为添加插件
lombok-mapstruct-binding
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<release>>${java.version}</release>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
3.1.2 mapStruct-swagger2
与 springfox-swagger2 冲突,该框架里也包含 mapstruct
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2 mapstruct-iterable-non-iterable
显示如何通过映射器将 util 类从可迭代元素转换为不可迭代元素
示例:
源 List<Integer> List<String>
目标 Integer(集合中的第一个元素) String(集合中的最后一个元素)
@Test
public void testToTarget() {
Source s = new Source();
s.setMyIntegers( Arrays.asList( 5, 3, 7 ) );
s.setMyStrings( Arrays.asList( "five", "three", "seven" ) );
Target t = SourceTargetMapper.MAPPER.toTarget( s );
// 取出集合中的第一个元素,为目标属性赋值
assertEquals( new Integer( 5 ), t.getMyInteger() );
// 集合中的最后一个元素,为目标属性赋值
assertEquals( "seven", t.getMyString() );
}
3.3 mapstruct-mapping-from-map
展示如何通过映射器 util 类和限定符在 Maps 上提取值。
示例1:
源:Map<String, Object>
目标:Bean (值来源于Map,属性名与Map中的key值相同)
@Test
public void testMapperOnExistingIpAndServer() {
Map<String, Object> map = new HashMap<>();
map.put("ip", "127.0.0.1");
map.put("server", "168.192.1.1");
Source s = new Source(map);
Target t = SourceTargetMapper.MAPPER.toTarget( s );
assertEquals(t.getIp(), "127.0.0.1");
assertEquals(t.getServer(), "168.192.1.1");
}
示例2:
源:Map<String, Object>
目标:Bean (值来源于Map,属性名与Map中的key值相同)
Bean中存在子bean(值来源于Map,属性名与Map中的key值不同)
@Test
public void shouldMapMapToBean() {
Map<String, String> map = new HashMap<>();
map.put("id", "1234");
map.put("name", "Tester");
map.put("did", "4321"); //Department Id
map.put("dname", "Test");// Depart name
Employee employee = MapToBeanMapper.INSTANCE.fromMap(map);
assertNotNull(employee);
assertEquals(employee.getId(), "1234");
assertEquals(employee.getName(), "Tester");
Department department = employee.getDepartment();
assertNotNull(department);
assertEquals(department.getId(), "4321");
assertEquals(department.getName(), "Test");
}
3.4 mapstruct-rounding
显示如何通过映射器 util 类和限定符对 Numbers 进行四舍五入。
示例:
源:BigDecimal
目标:BigDecimal
Util 限定符:
in == null ? null : in.setScale( 2, RoundingMode.DOWN );
3.5 mapstruct-updatemethods
显示如何更新现有目标对象。
注:目标有值,但是入参为null时,不会更新
示例:
源1:Bean
源2:值
目标:Bean
测试用例-说明:
源1:属性有值、源2:有值 ; 目标:new
源有值 = 目标属性有值,其他为null
源1:null、源2:有值 ; 目标:new
源有值 = 目标属性有值,其他为null
源1:new、源2:有值 ; 目标:new
源有值 = 目标属性有值,其他为null
源1:属性有值、源2:null ; 目标:new
源有值 = 目标属性有值,其他为null
源1:null、源2:null ; 目标:有值
目标属性保持原值
3.6 mapstruct-field-mapping
显示如何将 Map结构 与具有公共字段的类似结构的对象一起使用。
示例:
源:Long 、String 、Collection<OrderItem>
目标:Long 、String 、List<OrderItemDto>
OrderItem 与 OrderItemDto 属性名称一致
测试用例-说明:
源 --> 目标
目标--> 源
3.7 mapstruct-nested-bean-mappings
展示如何通过主根方法映射对象图
示例:
源:存在子Bean
目标:存在子Bean
多种映射方式
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", expression = "java(\"Jaws\")")
@Mapping(target = "plant", ignore = true )
@Mapping(target = "ornament", ignore = true )
@Mapping(target = "material", ignore = true)
@Mapping(target = "quality.document", source = "quality.report")
@Mapping(target = "quality.document.organisation.name", constant = "NoIdeaInc" )
FishTankWithNestedDocumentDto map(FishTank source);
3.8 mapstruct-mapping-with-cycles
展示如何映射可以包含循环的对象图
示例:
源:Bean 包含 List<Bean>
目标:Bean 包含 List<Bean>
源Bean 与 目标Bean 属性名不完全一致
3.9 mapstruct-spi-accessor-naming
有关如何使用服务提供程序接口 (SPI) 进行自定义访问器命名策略的示例。
示例:
SPI 定义Bean新的Getter、Setter方法规则
源、目标 引用了SPI
// get、set被定义为with
@Test
public void test() {
GolfPlayer golfPlayer1 = new GolfPlayer();
golfPlayer1.withName( "Tiger Woods" ).withHandicap( 12 );
GolfPlayerDto dto = GolfPlayerMapper.INSTANCE.toDto( golfPlayer1 );
GolfPlayer golfPlayer2 = GolfPlayerMapper.INSTANCE.toPlayer( dto );
Assert.assertEquals( "Tiger Woods", golfPlayer2.name() );
Assert.assertEquals( 12L, golfPlayer2.handicap(), 0 );
}
3.10 mapstruct-jpa-child-parent
如何在 JPA 中使用与父/子关系相关的@Context示例。
示例:
源:String 、List<ChildDto>
ChildDto: String
目标:String 、List<ChildEntity>
ChildEntity : String、目标父级ParentEntity
源 与 目标 属性名称一致
3.11 mapstruct-suppress-unmapped
显示在混合方案中,如何在默认情况下忽略映射到目标属性而不发出警告。但是,仍将应用具有相同名称的 Bean 属性映射。
3.12 mapstruct-lookup-entity-with-id
显示如何在映射方法中从数据库中读取具有复合键的对象。
示例:
源1:String1 String2
源2:String1 String2
目标:源1.String2 源2.String2
子Bean:源1.String1 源2.String1
@ObjectFactory 自定义初始化工厂
3.13 mapstruct-clone
显示如何通过定义所有映射方法深度克隆对象。
3.14 mapstruct-metadata-annotations
演示如何读取注解并将其用作映射指令。
3.15 mapstruct-mapper-repo
演示如何通过代码生成来构建映射器的存储库。
3.99 暂未实践的示例
- [mapstruct-protobuf3](mapstruct-protobuf3): protobuf3 和 MapStruct 的结合示例
- [mapstruct-on-ant](mapstruct-on-ant):展示如何在基于 Ant 的项目中使用 MapStruct;要构建此示例,请在命令行上运行“ant build”
- [mapstruct-on-gradle](mapstruct-on-gradle):展示如何在基于 Gradle 的项目中使用 MapStruct;若要生成示例项目,请在命令行上运行“./gradlew clean build”
- [mapstruct-on-bazel](mapstruct-on-bazel):展示如何在基于 Bazel 的项目中使用 MapStruct;若要生成示例项目,请在命令行上运行“Bazel Build //...”,若要测试项目,请运行“Bazel Test //...”。
- [mapstruct-kotlin](mapstruct-kotlin):使用 KAPT(Kotlin 注释处理工具)将 MapStruct 与 Kotlin 配合使用的示例
- [mapstruct-kotlin-gradle](mapstruct-kotlin-gradle):使用 KAPT 将 MapStruct 与 Kotlin 和 Gradle Kotlin DSL 配合使用的示例