安装
maven
...
<properties><org.mapstruct.version>1.6.3</org.mapstruct.version>
</properties>
...
<dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency>
</dependencies>
...
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source> <!-- depending on your project --><target>1.8</target> <!-- depending on your project --><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></path><!-- other annotation processors --></annotationProcessorPaths></configuration></plugin></plugins>
</build>
gradle
dependencies {...implementation 'org.mapstruct:mapstruct:1.6.3'annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'// If we are using mapstruct in test codetestAnnotationProcessor "org.mapstruct:mapstruct-processor:1.6.3"
}
与lombok集成
<properties><org.mapstruct.version>1.6.3</org.mapstruct.version><org.projectlombok.version>1.18.34</org.projectlombok.version><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version><scope>provided</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version></path><!-- additional annotation processor required as of Lombok 1.18.16 --><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>0.2.0</version></path></annotationProcessorPaths></configuration></plugin></plugins></build>
Mapper定义
只需要在接口上添加@Mapper注解即可,mapstruct会创建一个具有相同方法的实现类,并自动生成所有的setter和getter.
快速入门
@Data
@Builder
@ToString
public class BasicUser {private int id;private String name;
}
@Data
@Builder
@ToString
public class BasicUserDTO {private int id;private String name;
}
现在只需要定一个Mapper接口,并添加@Mapper注解就可以实现两个类型的相互转换:
@Mapper
public interface BasicMapper {// INSTANCE是自动生成的实现类BasicMapper INSTANCE = Mappers.getMapper(BasicMapper.class);// 只需要定义接口即可BasicUserDTO convert(BasicUser user);
}
生成的实现类:
public class BasicMapperImpl implements BasicMapper {@Overridepublic BasicUserDTO convert(BasicUser user) {// 很贴心的非空判断if ( user == null ) {return null;}BasicUserDTO.BasicUserDTOBuilder basicUserDTO = BasicUserDTO.builder();// 同名同类型参数映射basicUserDTO.id( user.getId() );basicUserDTO.name( user.getName() );return basicUserDTO.build();}
}
@Mapping
如果要映射的2个参数名或者类型不一致,可以使用@Mapping注解:
@Data
@Builder
@ToString
public class BasicUser {private Long id;private String username;
}
@Data
@Builder
@ToString
public class BasicUserDTO {private int id;private String name;
}
BasicUserDTO convert(BasicUser user);
生成的实现方法如下:
@Override
public BasicUserDTO convert(BasicUser user) {if ( user == null ) {return null;}BasicUserDTO.BasicUserDTOBuilder basicUserDTO = BasicUserDTO.builder();// 自动类型转换if ( user.getId() != null ) {basicUserDTO.id( user.getId().intValue() );}// name没有赋值return basicUserDTO.build();
}
添加@Mapping注解:
@Mapping(target = "name", source = "username")
BasicUserDTO convert(BasicUser user);
// 实现类
@Override
public BasicUserDTO convert(BasicUser user) {if ( user == null ) {return null;}BasicUserDTO.BasicUserDTOBuilder basicUserDTO = BasicUserDTO.builder();// name赋值了basicUserDTO.name( user.getUsername() );if ( user.getId() != null ) {basicUserDTO.id( user.getId().intValue() );}return basicUserDTO.build();
}
自定义映射方法
假如mapstruct默认实现无法满足我们的要求,我们还可以自定义映射方法,只需要在接口中添加一个default的方法即可:
public class UserDTO {private int id;private String name;
}
public class PersonDTO {private String id;private String firstName;private String lastName;
}
@Mapper
public interface BasicMapper {BasicMapper INSTANCE = Mappers.getMapper(BasicMapper.class);default PersonDTO convertCustom(UserDTO user) {return PersonDTO.builder().id(String.valueOf(user.getId())).firstName(user.getName().substring(0, user.getName().indexOf(" "))).lastName(user.getName().substring(user.getName().indexOf(" ") + 1)).build();}
}
多个数据源
// 3个数据源
public class UserDTO {private int id;private String name;
}
public class Education {private String degreeName;private String institute;private Integer yearOfPassing;
}
public class Address {private String houseNo;private String landmark;private String city;private String state;private String country;private String zipcode;
}
// target
public class PersonDTO {private String id;private String firstName;private String lastName;private String educationalQualification;private String residentialCity;private String residentialCountry;
}
// mapper
@Mapper
public interface BasicMapper {BasicMapper INSTANCE = Mappers.getMapper(BasicMapper.class);@Mapping(source = "education.degreeName", target = "educationalQualification")@Mapping(source = "address.city", target = "residentialCity")@Mapping(source = "address.country", target = "residentialCountry")@Mapping(source = "user.id", target = "id")@Mapping(expression = "java(extractFirstName(user))", target="firstName")@Mapping(expression = "java(extractLastName(user))", target="lastName")PersonDTO convertCustom(UserDTO user, Education education, Address address);// 名字用default方法手动处理default String extractFirstName(UserDTO user){if(user == null){return null;}return Optional.ofNullable(user.getName()).orElse("").split(" ")[0];}default String extractLastName(UserDTO user){if(user == null){return null;}String[] arr = Optional.ofNullable(user.getName()).orElse("").split(" ");return arr.length >= 2 ? arr[1] : "";}
}
// 自动生成的方法:@Override
public PersonDTO convertCustom(UserDTO user, Education education, Address address) {if ( user == null && education == null && address == null ) {return null;}PersonDTO.PersonDTOBuilder personDTO = PersonDTO.builder();if ( user != null ) {personDTO.id( String.valueOf( user.getId() ) );}if ( education != null ) {personDTO.educationalQualification( education.getDegreeName() );}if ( address != null ) {personDTO.residentialCity( address.getCity() );personDTO.residentialCountry( address.getCountry() );}personDTO.firstName( extractFirstName(user) );personDTO.lastName( extractLastName(user) );return personDTO.build();
}
映射内嵌对象
public class Manager {private int id;private String name;
}
public class ManagerDTO {private int id;private String name;
}
@Mapper
public interface ManagerMapper {ManagerMapper INSTANCE = Mappers.getMapper(ManagerMapper.class);ManagerDTO convert(Manager manager);
}
public class Education {private String degreeName;private String institute;private Integer yearOfPassing;
}
public class Address {private String houseNo;private String landmark;private String city;private String state;private String country;private String zipcode;
}
public class BasicUser {private int id;private String name;// 注意这个private List<Manager> managerList;
}
public class PersonDTO {private String id;private String firstName;private String lastName;private String educationalQualification;private String residentialCity;private String residentialCountry;private String designation;private long salary;private Education education;// 注意这个private List<ManagerDTO> managerList;
}
@Mapping(source = "user.id", target = "id")
@Mapping(source = "user.name", target = "firstName")
// 不添加这个映射也可以的,可以根据类性推断出来
@Mapping(source = "user.managerList", target = "managerList")
@Mapping(source = "education.degreeName", target = "educationalQualification")
@Mapping(source = "address.city", target = "residentialCity")
@Mapping(source = "address.country", target = "residentialCountry")
PersonDTO convert(BasicUser user, Education education, Address address);
//生成的方法
@Override
public PersonDTO convert(BasicUser user, Education education, Address address) {if ( user == null && education == null && address == null ) {return null;}PersonDTO.PersonDTOBuilder personDTO = PersonDTO.builder();if ( user != null ) {personDTO.id( String.valueOf( user.getId() ) );personDTO.firstName( user.getName() );// 这个自动去映射了内嵌的对象personDTO.managerList( managerListToManagerDTOList( user.getManagerList() ) );}if ( education != null ) {personDTO.educationalQualification( education.getDegreeName() );}if ( address != null ) {personDTO.residentialCity( address.getCity() );personDTO.residentialCountry( address.getCountry() );}return personDTO.build();
}protected List<ManagerDTO> managerListToManagerDTOList(List<Manager> list) {if ( list == null ) {return null;}List<ManagerDTO> list1 = new ArrayList<ManagerDTO>( list.size() );for ( Manager manager : list ) {list1.add( managerMapper.convert( manager ) );}return list1;
}
如果UserMapper和ManagerMapper不在同一个包,为了可以重复使用ManagerMapper,可以在UserMapper中导入ManagerMapper:
// 导入ManagerMapper
@Mapper(uses = {ManagerMapper.class})
public interface UserMapper {UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);@Mapping(source = "user.id", target = "id")@Mapping(source = "user.name", target = "firstName")@Mapping(source = "user.managerList", target = "managerList")@Mapping(source = "education.degreeName", target = "educationalQualification")@Mapping(source = "address.city", target = "residentialCity")@Mapping(source = "address.country", target = "residentialCountry")PersonDTO convert(BasicUser user, Education education, Address address);
}
//生成的方法如下:
public class UserMapperImpl implements UserMapper {// 这里先把ManagerMapper导入进来private final ManagerMapper managerMapper = ManagerMapper.INSTANCE;@Overridepublic PersonDTO convert(BasicUser user, Education education, Address address) {if ( user == null && education == null && address == null ) {return null;}PersonDTO.PersonDTOBuilder personDTO = PersonDTO.builder();if ( user != null ) {personDTO.id( String.valueOf( user.getId() ) );personDTO.firstName( user.getName() );personDTO.managerList( managerListToManagerDTOList( user.getManagerList() ) );}if ( education != null ) {personDTO.educationalQualification( education.getDegreeName() );}if ( address != null ) {personDTO.residentialCity( address.getCity() );personDTO.residentialCountry( address.getCountry() );}return personDTO.build();}protected List<ManagerDTO> managerListToManagerDTOList(List<Manager> list) {if ( list == null ) {return null;}List<ManagerDTO> list1 = new ArrayList<ManagerDTO>( list.size() );for ( Manager manager : list ) {// 直接调用managerMapper的方法list1.add( managerMapper.convert( manager ) );}return list1;}
}
隐式类型转换
数字格式化
public class Employee {private String name;private long salary;
}
public class PersonDTO {private String id;private String firstName;private String lastName;private long salary;
}
@Mapping(target="salary", source = "salary", numberFormat = "$#.00")
PersonDTO convert(Employee employee);
//生成的方法:
@Override
public PersonDTO convert(Employee employee) {if ( employee == null ) {return null;}PersonDTO.PersonDTOBuilder personDTO = PersonDTO.builder();personDTO.salary( new DecimalFormat( "$#.00" ).format( employee.getSalary() ) );return personDTO.build();
}
日期格式化
public class Employee {private Date dateOfBirth;
}
public class PersonDTO {private String birthday;
}
@Mapping(target = "birthday", source = "dateOfBirth", dateFormat = "yyyy-MM-dd")
PersonDTO convert(Employee employee);
// 生成的方法
if ( employee.getDateOfBirth() != null ) {personDTO.birthday( new SimpleDateFormat( "yyyy-MM-dd" ).format( employee.getDateOfBirth() ) );
}
默认值或者常量
public class Employee {private String name;private String country;
}
public class PersonDTO {private String id;private String firstName;private String lastName;private String country;private String city;
}
// source中必须存在这个属性,当值为null的时候,则启用
@Mapping(target="country", defaultValue = "CHINA")
// source中有没有这个属性都行
@Mapping(target="city", constant = "JINAN")
PersonDTO convert(Employee employee);
//生成的方法:
@Override
public PersonDTO convert(Employee employee) {if ( employee == null ) {return null;}PersonDTO.PersonDTOBuilder personDTO = PersonDTO.builder();if ( employee.getCountry() != null ) {personDTO.country( employee.getCountry() );}else {personDTO.country( "CHINA" );}personDTO.city( "JINAN" );return personDTO.build();
}
表达式
public class Employee {private String firstName;private String lastName;
}
public class PersonDTO {private String id;private String name;
}
// mapper, 注意这里的import
// import是导入一个普通的java类,而use是导入另一个Mapper类
@Mapper(imports = UUID.class)
public interface PersonMapper {@Mapping(target="id", expression = "java(UUID.randomUUID().toString())")@Mapping(target="name", expression = "java(createName(employee))")PersonDTO convert(Employee employee);default String createName(Employee employee){if(employee == null){return null;}return employee.getFirstName() + " " + employee.getLastName();}
}
// 生成的方法:
@Override
public PersonDTO convert(Employee employee) {if ( employee == null ) {return null;}PersonDTO.PersonDTOBuilder personDTO = PersonDTO.builder();personDTO.id( UUID.randomUUID().toString() );personDTO.name( createName(employee) );return personDTO.build();
}
与Spring集成
public class BasicUser {private int id;private String name;
}
public class BasicUserDTO {private int id;private String name;
}
// mapper,注意看这里的componentModel, 这样就可以把BasicUserMapper 注入到Spring容器中
@Mapper(componentModel = "spring")
public interface BasicUserMapper {BasicUserMapper INSTANCE = Mappers.getMapper(BasicUserMapper.class);BasicUserDTO convert(BasicUser user);
}
// 测试一下
@SpringBootTest
class UserMapperTest {@Autowiredprivate BasicUserMapper userMapper;@Testpublic void testConvert(){BasicUserDTO userDTO = userMapper.convert(BasicUser.builder().id(1).name("name1").build());Assertions.assertEquals(userDTO.getId(), 1);Assertions.assertEquals(userDTO.getName(), "name1");}
}
测试
可以直接new一个实现类进行测试:
@Test
public void testConvert(){// new一个实现类BasicUserMapperImpl userMapper = new BasicUserMapperImpl();BasicUser user = BasicUser.builder().id(1).name("name1").build();BasicUserDTO userDTO = userMapper.convert(user);Assertions.assertEquals(userDTO.getId(), 1);Assertions.assertEquals(userDTO.getName(), "name1");
}