文章目录
一、MapStruct简介
MapStruct 是一个 Java Bean mapper,用于Java Bean 之间的转换。MapStruct 基于约定优于配置的设计思想,相较于常用的 BeanUtils.copyProperties 它更高效、优雅。
使用 MapStruct,我们只需要定义映射接口,该库在编译时会自动生成具体实现代码。
二、使用
1、引包
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.3</version>
</dependency>
<!--lombok支持-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</dependency>
<!-- jdk8要额外引入 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.6.3</version>
</dependency>
2、基本原理
1、首先,我们定义两个Java类, SimpleSource 是源类,SimpleDestination 是要映射的目标类,他们具有相同的字段。
@Data
public class SimpleSource {
private String name;
private String description;
// getters and setters
}
@Data
public class SimpleDestination {
private String name;
private String description;
// getters and setters
}
2、Mapper 接口定义
在MapStruct中,我们将类型转换器称为 mapper,请不要和Mybatis中的mapper混淆。
下面,我们定义mapper接口。使用@Mapper注解,告诉MapStruct这是我们的映射器。
注意,我们只需定义接口即可,MapStruct 会自定创建对应的实现类。
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SimpleSourceDestinationMapper {
SimpleSourceDestinationMapper INSTANCE = Mappers.getMapper(SimpleSourceDestinationMapper.class);
SimpleDestination sourceToDestination(SimpleSource source);
SimpleSource destinationToSource(SimpleDestination destination);
}
3、自动生成Mapper代码
我们重新build之后,会生成相关代码:
4、使用
public class Test {
public static void main(String[] args) {
// 创建源对象并赋值
SimpleSource simpleSource = new SimpleSource();
simpleSource.setName("SourceName");
simpleSource.setDescription("SourceDescription");
SimpleDestination simpleDestination = SimpleSourceDestinationMapper.INSTANCE.sourceToDestination(simpleSource);
// 属性赋值了
System.out.println(simpleDestination.getName().equals(simpleSource.getName()));
System.out.println(simpleDestination.getDescription().equals(simpleSource.getDescription()));
}
}
3、属性名相同时映射(包含子类型、枚举-深拷贝)
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SimpleSourceDestinationMapper {
SimpleSourceDestinationMapper INSTANCE = Mappers.getMapper(SimpleSourceDestinationMapper.class);
// 直接转换即可
SimpleDestination sourceToDestination(SimpleSource source);
SimpleSource destinationToSource(SimpleDestination destination);
}
4、属性名不同时映射
//需要使用@Mapping注解指定转换字段的名称
//解释:source中的number转换为tagert中的myNumber
@Mapping(source = "number", target = "myNumber")
Target differentNameConvert(Source source);
// before Java 8 java8之前
@Mapper
public interface MyMapper {
@Mappings({
@Mapping(target = "firstProperty", source = "first"),
@Mapping(target = "secondProperty", source = "second")
})
HumanDto toHumanDto(Human human);
}
// Java 8 and later java8(包含)以后
@Mapper
public interface MyMapper {
@Mapping(target = "firstProperty", source = "first"),
@Mapping(target = "secondProperty", source = "second")
HumanDto toHumanDto(Human human);
}
5、默认值的使用
//需使用@Mapping中的defaultValue字段进行标识
//解释:若age字段为null,则使用defaultValue的值
@Mapping(source = "age", target = "age", defaultValue = "34")
Target defaultValueConvert(Source source);
6、日期格式映射
@Mapping(target="employeeId", source = "entity.id")
@Mapping(target="employeeName", source = "entity.name")
@Mapping(target="employeeStartDt", source = "entity.startDt",
dateFormat = "dd-MM-yyyy HH:mm:ss")
EmployeeDTO employeeToEmployeeDTO(Employee entity);
@Mapping(target="id", source="dto.employeeId")
@Mapping(target="name", source="dto.employeeName")
@Mapping(target="startDt", source="dto.employeeStartDt",
dateFormat="dd-MM-yyyy HH:mm:ss")
Employee employeeDTOtoEmployee(EmployeeDTO dto);
//需使用@mapping中的dateFormat注解进行标识转换的形式
//解释:source.date字段转换为target中的stringDate,转换格式为dateFormat指定的格式
@Mappings({
@Mapping(source = "source.date", target = "stringDate", dateFormat = "yyyy-MM-dd HH:mm:ss"), //Date转换成String
@Mapping(source = "source.stringDate", target = "date", dateFormat = "yyyy-MM-dd HH:mm") //String转换成Date
})
Target childenConvert(Source source);
7、常量的使用
//需使用@Mapping中的constant字段进行标识
//解释:不论target中的oldNumber转换值是什么,最后都使用constant的值
@Mapping(target = "oldNumber", constant = "35")
Target constantConvert(Source source);
8、忽略某个字段转换
//需使用@Mapping中的ignore字段进行标识
//解释:忽略target中的name字段转换 不论source中name的值是什么 最后tagert中的name值都是null
@Mapping(target = "name", ignore = true)
Target ignoreConvert(Source source);
9、单独参数转换
//需标明单独参数转换后的字段名称
//解释:将传入的sex字段传递给target中的sex
@Mapping(source = "sex", target = "sex")
Target aloneParmConvert(Integer sex);
10、更新数据源
//需使用@MappingTarget字段标明
//解释:将SonTarget中的值更新给Target 要改变谁 在谁前面加@MappingTarget
void updateTargetConvert(@MappingTarget Target target, SonTarget sonTarget);
11、自定义数据类型转换
// java8
@Mapper
public interface CarMapper {
@Mapping(...)
...
CarDto carToCarDto(Car car);
default PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
@Mapper
public abstract class CarMapper {
@Mapping(...)
...
public abstract CarDto carToCarDto(Car car);
public PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}
public class FishTank {
Fish fish;
String material;
Quality quality;
int length;
int width;
int height;
}
public class FishTankWithVolumeDto {
FishDto fish;
MaterialDto material;
QualityDto quality;
VolumeDto volume;
}
public class VolumeDto {
int volume;
String description;
}
@Mapper
public abstract class FishTankMapperWithVolume {
@Mapping(target = "fish.kind", source = "source.fish.type")
@Mapping(target = "material.materialType", source = "source.material")
@Mapping(target = "quality.document", source = "source.quality.report")
@Mapping(target = "volume", source = "source")
abstract FishTankWithVolumeDto map(FishTank source);
VolumeDto mapVolume(FishTank source) {
int volume = source.length * source.width * source.height;
String desc = volume < 100 ? "Small" : "Large";
return new VolumeDto(volume, desc);
}
}
12、多数据源转换
//需使用@Mapping标明转换时参数传递的规则
//解释:将source中的ignore字段值传递到target的ignore 将sourceTwo的id字段值传递到target中的id
@Mappings({
@Mapping(source = "source.ignore", target = "ignore"),
@Mapping(source = "sourceTwo.id", target = "id")
})
SonTarget multiSourceConvert(Source source, SourceTwo sourceTwo);
13、表达式的使用
//需使用@Mapping中的expression字段标识所使用的表达式
//解释:引用Java表达式中DateTuil中的date方法处理date的转换逻辑 全路径
@Mapping(target = "stringDate", expression = "java(cn.hutool.core.date.DateUtil.date(date))")
SonTarget expressionConvert(Date date);
//解释:可在接口上面的@Mapper中引入某个类,这样就可以直接写方法,不用写全路径
@Mapper(imports = {DateUtil.class})
@Mapping(target = "date", expression = "java(DateUtil.date(date))")
SonTarget expressionConvert(Date date);
14、子父类转换
//需使用@BeanMapping指定返回的类型
//解释:SonTarget是Target的子类,该方法用父类接受,返回子类属性
@BeanMapping(resultType = SonTarget.class)
Target childenConvert(Source source);
15、逆向转换
//需使用InheritInverseConfiguration标识,该注解是根据源和目标的类型去自动识别,若存在多个相同源转换目标的方法时,需指定逆向转换的方法
//解释:将differentNameConvert方法进行逆向转换
//优点:若原differentNameConvert方法上有很多@Mapping规则的话,无需再次书写即可使用
@InheritInverseConfiguration(name = "differentNameConvert")
Source convertToSource(Target target);
16、numberFormat的使用
//解释:String转数字类型后若想指定数字类型的格式可使用numberFormat指定
@Mapping(target = "number", numberFormat = "#.00")
Target differentTypeConvert(Source source);
17、@BeforeMapping的使用
//解释:在转换之前对某个实体进行操作
@BeforeMapping
default void beforeConvert(Source source){
// some mapping logic
}
18、@AfterMapping的使用
//解释:在转换之后对某个实体进行操作
@AfterMapping
default void afterConvert(Source source){
// some mapping logic
}
19、集合转换-无特殊要求
//解释:直接定义转换方法即可,MapStruct会自动生成Source转换成Target的方法,然后循环去调用
List<Target> convertToTargets(List<Source> sources);
20、集合转换-需自定义规则
//解释:自定义Source转换Target规则,MapStruct会自动调用sourceConvertToTarget方法来替代他自己生成的方法
//此时自定义规则才会生效
List<Target> convertToTargets(List<Source> sources);
@Mapping(source = "name", target = "name2")
Target sourceConvertToTarget(Source source);
集合转换-需自定义规则(存在多个自定义规则)
//解释:存在多个转换方法时,MapStruct无法自动选择,此时需使用@IterableMapping与@Named联合指定
//qualifiedByName指定调用的转换规则,@Named标明被调用的名称,这里的两个名称需一致
@IterableMapping(qualifiedByName = "sourceTwoConvertToTarget")
List<Target> convertToTargets(List<SourceTwo> sourceTwos);
@Mapping(source = "name", target = "name2")
@Named("sourceTwoConvertToTarget")
Target sourceTwoConvertToTarget(SourceTwo sourceTwo);
Target sourceTwoConvertToTarge2(SourceTwo sourceTwo);
21、多层嵌套映射
@Mapper
public interface CustomerMapper {
// 用.表示对象层级关系
@Mapping( target = "name", source = "record.name" )
@Mapping( target = ".", source = "record" )
@Mapping( target = ".", source = "account" )
Customer customerDtoToCustomer(CustomerDto customerDto);
}
22、spring依赖注入
某些时候尤其是在做项目时,我们用到了Sping,希望映射后的新实例是交给Spring管理。这时候就需要进行依赖注入了。只需要在Mapper接口中的@Mapper注解中加入componentModel = "spring"即可。
三、高级用法
1、自定义注解
可以重复使用@ToEntity
@Retention(RetentionPolicy.CLASS)
@Mapping(target = "id", ignore = true)
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
@Mapping(target = "name", source = "groupName")
public @interface ToEntity { }
@Mapper
public interface StorageMapper {
StorageMapper INSTANCE = Mappers.getMapper( StorageMapper.class );
@ToEntity
@Mapping( target = "weightLimit", source = "maxWeight")
ShelveEntity map(ShelveDto source);
@ToEntity
@Mapping( target = "label", source = "designation")
BoxEntity map(BoxDto source);
}
2、逆映射
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class );
@Mapping(target = "name", source = "customerName")
Customer toCustomer(CustomerDto customerDto);
@InheritInverseConfiguration
CustomerDto fromCustomer(Customer customer);
}
3、Map映射(同对象)
@Mapper
public interface CustomerMapper {
@Mapping(target = "name", source = "customerName")
Customer toCustomer(Map<String, String> map);
}
4、调用其他映射器
public class DateMapper {
public String asString(Date date) {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
.format( date ) : null;
}
public Date asDate(String date) {
try {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
.parse( date ) : null;
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
}
}
@Mapper(uses=DateMapper.class)
public interface CarMapper {
CarDto carToCarDto(Car car);
}
参考资料
https://www.baeldung-cn.com/mapstruct
https://mapstruct.org/documentation/stable/reference/html/#non-shipped-annotations