<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>1.5.3.Finalversion>
dependency>
我们现在有三个类,分别是PersonDO、PersonDTO、PersonVO。
public class PersonDO {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private String age;
/**
* 职业
*/
private String job;
/**
* 国籍
*/
private String country;
/**
* 身高
*/
private Double height;
/**
* 体重
*/
private Double weight;
// constructor、Getter、Setter etc.
public class PersonDTO {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private String age;
/**
* 职业
*/
private String occupation;
/**
* 国籍
*/
private String country;
/**
* 身高
*/
private Double height;
/**
* 体重
*/
private Double weight;
// constructor、Getter、Setter etc.
public class PersonVO {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private String age;
/**
* 职业
*/
private String occupation;
/**
* 国籍
*/
private String country;
/**
* 身高
*/
private Double height;
/**
* 体重
*/
private Double weight;
/**
* 身体质量指数
*/
private Double bodyMassIndex;
// constructor、Getter、Setter etc.
可以看到三个类都是关于Person的类,但是应用在不同的层,其中的某些字段名称也有一些区别。
首先,我们想要将PersonDO的属性给到PersonDTO。我们首先建一个Converter的接口。
// MapStruct注解
@Mapper
public interface PersonConverter {
PersonConverter CONVERTER = Mappers.getMapper(PersonConverter.class);
/**
* PersonDO 2 PersonDTO
* @param personDO
* @return
*/
PersonDTO DO2DTO(PersonDO personDO);
}
我们建了一个接口,叫做PersonConverter
接口,用了@Mapper
注解表明这个接口是一个MapStruct转换接口。此外,我们还定义了一个方法:DO2DTO
,可以看到这个方法的入参数PersonDO
,出参是PersonDTO
。Maptruct会根据定义方法的出入参自动生成转换逻辑。我们Build一下,会自动生成Converter接口的实现类。我们看一下生成的逻辑:
@Override
public PersonDTO DO2DTO(PersonDO personDO) {
if ( personDO == null ) {
return null;
}
PersonDTO personDTO = new PersonDTO();
personDTO.setName( personDO.getName() );
personDTO.setAge( personDO.getAge() );
personDTO.setHeight( personDO.getHeight() );
personDTO.setWeight( personDO.getWeight() );
return personDTO;
}
OK,我们看到这里转换的逻辑已经成功生成了,那么接下来就是在我们的代码里面调用一下:
PersonDTO personDTO = PersonConverter.CONVERTER.DO2DTO(personDO);
通过这样的调用,我们就能完成Bean的转换。这里需要注意的是,每个类都需要实现get
和set
方法,否则生成的逻辑里面没有赋值的代码。
1.1中介绍了单Bean的点对点转换,但是有的场景中,我们的Bean是装在列表里的,我们还需要手写个列表的遍历逻辑?其实MapStruct是支持批量转换的,我们在我们的Converter接口里定义如下方法:
/**
* PersonDOList 2 PersonDTOList
* @param personDOList
* @return
*/
List<PersonDTO> DOList2DTOList(List<PersonDO> personDOList);
我们看一下生成的转换逻辑:
@Override
public List<PersonDTO> DOList2DTOList(List<PersonDO> personDOList) {
if ( personDOList == null ) {
return null;
}
List<PersonDTO> list = new ArrayList<PersonDTO>( personDOList.size() );
for ( PersonDO personDO : personDOList ) {
list.add( DO2DTO( personDO ) );
}
return list;
}
MapStruct自动实现了遍历列表的逻辑,很方便。
我们可以看到,PersonDO里面有个属性是job
,是描述职业的属性,在生成的转换逻辑里面并没有这个字段的转换,应为在PersonDTO里,职业这个属性叫做occupation
。虽然是两个属性的含义是一样的,但是名称不一样,我们同样希望自动转换的时候,MapStruct能够实现这两个属性的复制。为此,我们需要显示的告诉Mapstruct转换的映射关系:
// 映射关系注解
@Mapping(target = "occupation", source = "job")
PersonDTO DO2DTO(PersonDO personDO);
我们为上文的DO2DTO
方法添加了@Mapping
注解,通过用@Mapping
这个注解就能够告诉MapStruct转换时的映射关系,其中的target
是目标Bean的属性名称,job
是源Bean的属性名称。我们看一下生成的转换逻辑:
@Override
public PersonDTO DO2DTO(PersonDO personDO) {
if ( personDO == null ) {
return null;
}
PersonDTO personDTO = new PersonDTO();
// job -> occupation
personDTO.setOccupation( personDO.getJob() );
personDTO.setName( personDO.getName() );
personDTO.setAge( personDO.getAge() );
personDTO.setHeight( personDO.getHeight() );
personDTO.setWeight( personDO.getWeight() );
return personDTO;
}
我们可以看到,自动生成的逻辑里面已经有了job
到occupation
的转换。
在Person
中,不仅职业这个属性的名称不一样,国籍这个属性也不一样。在PersonDO
中叫country
,在PersonDTO
中叫nation
。我们希望职业和国籍两个属性在转换的过程中都能被成功赋值,那么就需要用到多字段映射的能力:
@Mappings({
@Mapping(target = "occupation", source = "job"),
@Mapping(target = "nation", source = "country")
})
PersonDTO DO2DTO(PersonDO personDO);
我们用@Mappings
注解将两个属性的映射关系都囊括在内,就能实现多字段的映射处理,生成的逻辑就不展示了。
PersonDO
到PersonDTO
的转换我们搞定了,但是PersonDTO
到PersonVO
的转换还没搞定呢。PersonVO
中有个bodyMassIndex
(BMI)属性,在PersonDTO
中是没有的。我们知道BMI是描述身体胖瘦的一个指标,是根据人体的身高的体重算出来的。那我们在转换的过程中,需要实现这个转换的逻辑。BMI的计算公式为
B
M
I
=
w
e
i
g
h
t
h
e
i
g
h
t
2
BMI = \frac{weight}{height^2}
BMI=height2weight
那我们来造一个BMI的计算器::
public class BMICalculator {
/**
* 计算BMI
* @param weight
* @param height
* @return BMI
*/
public static Double calculate(Double weight, Double height) {
return weight / (height * height);
}
}
然后我们自定义转换时的映射表达式:
@Mapper
public interface PersonConverter {
PersonConverter CONVERTER = Mappers.getMapper(PersonConverter.class);
/**
* PersonDTO 2 PersonVO
* @param personDTO
* @return
*/
@Mapping(target = "bodyMassIndex", expression = "java(org.example.BMICalculator.calculate(personDTO.getWeight(), personDTO.getHeight()))")
PersonVO DTO2VO(PersonDTO personDTO);
}
我们在PersonConverter
里定义了一个新方法,用于PersonDTO
到PersonVO
的转换,为了实现bodyMassIndex
属性的转换,我们还用了@Mapping
注解指定了一个映射的关系,其中target
是目标Bean的属性名称,expression
就是我们定制的逻辑表达式。看一下生成的逻辑:
@Override
public PersonVO DTO2VO(PersonDTO personDTO) {
if ( personDTO == null ) {
return null;
}
PersonVO personVO = new PersonVO();
personVO.setName( personDTO.getName() );
personVO.setAge( personDTO.getAge() );
personVO.setOccupation( personDTO.getOccupation() );
personVO.setNation( personDTO.getNation() );
personVO.setHeight( personDTO.getHeight() );
personVO.setWeight( personDTO.getWeight() );
// BMI定制逻辑
personVO.setBodyMassIndex( org.example.BMICalculator.calculate(personDTO.getWeight(), personDTO.getHeight()) );
return personVO;
}
我们能够看到,这里的定制逻辑实际上就是把@Mapping
注解里expression
的内容作为代码直接执行的。为了防止PersonConverter
的实现类找不到我们定制的代码段,建议定制的代码段写全类名,此处我写的是org.example.BMICalculator.calculate()
,同时定制的代码段参数名称要跟定义的转换接口的参数名称一致,否则也会报错
我们来测试一下转换的效果:
public class Main {
public static void main(String[] args) throws InterruptedException {
PersonDTO personDTO = new PersonDTO();
// 身高178cm
personDTO.setHeight(1.78);
// 体重68kg
personDTO.setWeight(68.0);
PersonVO personVO = PersonConverter.CONVERTER.DTO2VO(personDTO);
System.out.println(personVO);
}
}
输出的结果为:
PersonVO{
name='null',
age='null',
occupation='null',
nation='null',
height=1.78,
weight=68.0,
bodyMassIndex=21.461936624163616
}
成功地计算出了BMI的值。