一个工程中,我们会定义各种DO,DTO,VO等pojo。不同的pojo转化有两种方式,一是写getter/setter方法,另外一种是用BeanUtils.copyProperties(),第一种太繁琐,第二种不好定位问题,同时反射损耗性能。mapstruct完美解决上述问题。
mapstruct是一个java注解处理器,用来生成各种类型安全的映射类。主要优势如下:
...
<properties>
<org.mapstruct.version>1.4.1.Finalorg.mapstruct.version>
properties>
...
<dependencies>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>${org.mapstruct.version}version>
dependency>
dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${org.mapstruct.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
...
如果你的工程使用到lombok,请用以下配置:
...
<properties>
<org.mapstruct.version>1.4.1.Finalorg.mapstruct.version>
<lombok.version>1.18.12lombok.version>
properties>
...
<dependencies>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>${org.mapstruct.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${org.mapstruct.version}version>
path>
<path>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
...
直接上代码:
package com.freedom.code.mapper;
import com.freedom.code.dto.UserDTO;
import com.freedom.code.entity.UserDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO doToDto(UserDO userDO);
}
执行编译后,可以在target文件找到如下类:
package com.freedom.code.mapper;
import com.freedom.code.dto.UserDTO;
import com.freedom.code.entity.UserDO;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-01-03T22:29:23+0800",
comments = "version: 1.4.1.Final, compiler: javac, environment: Java 1.8.0_271 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {
@Override
public UserDTO doToDto(UserDO userDO) {
if ( userDO == null ) {
return null;
}
UserDTO userDTO = new UserDTO();
userDTO.setCreateBy( userDO.getCreateBy() );
userDTO.setCreateByName( userDO.getCreateByName() );
userDTO.setCreateDate( userDO.getCreateDate() );
userDTO.setUpdateBy( userDO.getUpdateBy() );
userDTO.setUpdateByName( userDO.getUpdateByName() );
userDTO.setUpdateDate( userDO.getUpdateDate() );
userDTO.setVersion( userDO.getVersion() );
userDTO.setId( userDO.getId() );
userDTO.setName( userDO.getName() );
userDTO.setAge( userDO.getAge() );
userDTO.setEmail( userDO.getEmail() );
return userDTO;
}
}
可以看到mapstruct自动帮你生成实现类,自动帮你映射字段。
使用:
package com.freedom.code;
import com.freedom.code.dto.UserDTO;
import com.freedom.code.entity.UserDO;
import com.freedom.code.mapper.UserMapper;
import com.freedom.code.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
// 这里只写SpringBootTest这个注解; 如果是junit4的话, 就要加上@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
@Autowired
private UserService userService;
@Test
public void testMapper1() {
UserDO userDO = userService.getById("1345368146033819649");
UserDTO userDTO = UserMapper.INSTANCE.doToDto(userDO);
System.out.println(userDTO);
}
}
是不是很简单。
如果字段名不同,可以使用@Mapping。
@Mapping(target = "userId",source = "id")
@Mapping(target = "username",source = "name")
UserVO doToVo(UserDO userDO);
MapStruct支持使用原生注解,@Mapping支持原生注解@Target的ElementType.METHOD, ElementType.ANNOTATION_TYPE两种类型。
例子如下:
@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);
}
使用条件:
有些时候mapstruct无法生成某些类型的转化方法,这个时候我们可以手动生成。
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(target = "roleDTO", source = "roleDO")
UserDTO doToDto(UserDO userDO);
// 手动写的映射逻辑,假设mapstruct不能自动生成
default RoleDTO doToDto(RoleDO roleDO) {
if (roleDO == null) {
return null;
}
RoleDTO roleDTO = new RoleDTO();
roleDTO.setId(roleDO.getId());
roleDTO.setName(roleDO.getName());
return roleDTO;
}
}
生成的代码如下
public class UserMapperImpl implements UserMapper {
@Override
public UserDTO doToDto(UserDO userDO) {
if ( userDO == null ) {
return null;
}
UserDTO userDTO = new UserDTO();
// mapstruct自动应用你所写的映射方法
userDTO.setRoleDTO( doToDto( userDO.getRoleDO() ) );
userDTO.setCreateBy( userDO.getCreateBy() );
userDTO.setCreateByName( userDO.getCreateByName() );
userDTO.setCreateDate( userDO.getCreateDate() );
userDTO.setUpdateBy( userDO.getUpdateBy() );
userDTO.setUpdateByName( userDO.getUpdateByName() );
userDTO.setUpdateDate( userDO.getUpdateDate() );
userDTO.setVersion( userDO.getVersion() );
userDTO.setId( userDO.getId() );
userDTO.setName( userDO.getName() );
userDTO.setAge( userDO.getAge() );
userDTO.setEmail( userDO.getEmail() );
return userDTO;
}
除了以上接口形式,也可以在抽象类中定义方法,如下:
@Mapper
public abstract class UserMapper {
public static UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(target = "roleDTO", source = "roleDO")
public abstract UserDTO doToDto(UserDO userDO);
public RoleDTO doToDto(RoleDO roleDO) {
if (roleDO == null) {
return null;
}
RoleDTO roleDTO = new RoleDTO();
roleDTO.setId(roleDO.getId());
roleDTO.setName(roleDO.getName());
return roleDTO;
}
}
生成代码如下:
public class UserMapperImpl extends UserMapper {
@Override
public UserDTO doToDto(UserDO userDO) {
if ( userDO == null ) {
return null;
}
UserDTO userDTO = new UserDTO();
userDTO.setRoleDTO( doToDto( userDO.getRoleDO() ) );
userDTO.setCreateBy( userDO.getCreateBy() );
userDTO.setCreateByName( userDO.getCreateByName() );
userDTO.setCreateDate( userDO.getCreateDate() );
userDTO.setUpdateBy( userDO.getUpdateBy() );
userDTO.setUpdateByName( userDO.getUpdateByName() );
userDTO.setUpdateDate( userDO.getUpdateDate() );
userDTO.setVersion( userDO.getVersion() );
userDTO.setId( userDO.getId() );
userDTO.setName( userDO.getName() );
userDTO.setAge( userDO.getAge() );
userDTO.setEmail( userDO.getEmail() );
return userDTO;
}
例子:
@Mapping(target = "username", source = "userDO.name")
@Mapping(target = "roleName", source = "roleDO.name")
@Mapping(target = "age", source = "myAge")
UserRoleDTO doToUserRoleDto(UserDO userDO, RoleDO roleDO, Integer myAge);
生成代码如下:
public class UserMapperImpl implements UserMapper {
@Override
public UserRoleDTO doToUserRoleDto(UserDO userDO, RoleDO roleDO, Integer myAge) {
if ( userDO == null && roleDO == null && myAge == null ) {
return null;
}
UserRoleDTO userRoleDTO = new UserRoleDTO();
if ( userDO != null ) {
userRoleDTO.setUsername( userDO.getName() );
}
if ( roleDO != null ) {
userRoleDTO.setRoleName( roleDO.getName() );
}
if ( myAge != null ) {
userRoleDTO.setAge( myAge );
}
return userRoleDTO;
}
}
例子:
@Mapper
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);
@Mapping( target = "name", source = "record.name" )
@Mapping( target = ".", source = "record" )
@Mapping( target = ".", source = "account" )
CustomerDO customerDtoToCustomer(CustomerDTO customerDto);
}
生成代码如下:
public class CustomerMapperImpl implements CustomerMapper {
@Override
public CustomerDO customerDtoToCustomer(CustomerDTO customerDto) {
if ( customerDto == null ) {
return null;
}
CustomerDO customerDO = new CustomerDO();
customerDO.setName( customerDtoRecordName( customerDto ) );
customerDO.setBuyDate( customerDtoRecordBuyDate( customerDto ) );
customerDO.setBankName( customerDtoAccountBankName( customerDto ) );
customerDO.setAmount( customerDtoAccountAmount( customerDto ) );
customerDO.setId( customerDto.getId() );
return customerDO;
}
private String customerDtoRecordName(CustomerDTO customerDTO) {
if ( customerDTO == null ) {
return null;
}
Record record = customerDTO.getRecord();
if ( record == null ) {
return null;
}
String name = record.getName();
if ( name == null ) {
return null;
}
return name;
}
private LocalDateTime customerDtoRecordBuyDate(CustomerDTO customerDTO) {
if ( customerDTO == null ) {
return null;
}
Record record = customerDTO.getRecord();
if ( record == null ) {
return null;
}
LocalDateTime buyDate = record.getBuyDate();
if ( buyDate == null ) {
return null;
}
return buyDate;
}
private String customerDtoAccountBankName(CustomerDTO customerDTO) {
if ( customerDTO == null ) {
return null;
}
Account account = customerDTO.getAccount();
if ( account == null ) {
return null;
}
String bankName = account.getBankName();
if ( bankName == null ) {
return null;
}
return bankName;
}
private BigDecimal customerDtoAccountAmount(CustomerDTO customerDTO) {
if ( customerDTO == null ) {
return null;
}
Account account = customerDTO.getAccount();
if ( account == null ) {
return null;
}
BigDecimal amount = account.getAmount();
if ( amount == null ) {
return null;
}
return amount;
}
}
CustomerDTO.record和CustomerDTO.account中的每个属性都会映射到目标类的属性,如果有冲突,可以使用@Mapping( target = “name”, source = “record.name” )解决。record和account都有name属性,使用source = "record.name"解决冲突。
例子:
@Mapping(target = "roleDTO", source = "roleDO")
void doToDto(UserDO userDO, @MappingTarget UserDTO userDTO);
生成的代码:
@Override
public void doToDto(UserDO userDO, UserDTO userDTO) {
if ( userDO == null ) {
return;
}
userDTO.setRoleDTO( doToDto( userDO.getRoleDO() ) );
userDTO.setCreateBy( userDO.getCreateBy() );
userDTO.setCreateByName( userDO.getCreateByName() );
userDTO.setCreateDate( userDO.getCreateDate() );
userDTO.setUpdateBy( userDO.getUpdateBy() );
userDTO.setUpdateByName( userDO.getUpdateByName() );
userDTO.setUpdateDate( userDO.getUpdateDate() );
userDTO.setVersion( userDO.getVersion() );
userDTO.setId( userDO.getId() );
userDTO.setName( userDO.getName() );
userDTO.setAge( userDO.getAge() );
userDTO.setEmail( userDO.getEmail() );
}
如果字段是public修饰,如果也没setter和getter方法,mapstruct也可以支持。mapstruct把这种字段当做read/write accessor。
@Data
public class CarDO {
private Long id;
private String name;
}
public class CarDTO {
public Long id;
public String name;
}
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
CarDTO dtoToDo(CarDO carDO);
}
生成的代码:
public class CarMapperImpl implements CarMapper {
@Override
public CarDTO dtoToDo(CarDO carDO) {
if ( carDO == null ) {
return null;
}
CarDTO carDTO = new CarDTO();
carDTO.id = carDO.getId();
carDTO.name = carDO.getName();
return carDTO;
}
}
主要有两种:
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
CarDTO dtoToDo(CarDO carDO);
}
这种方式比较直接,方便使用。
另外一种方式:
@Mapper(componentModel = "spring")
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
CarDTO dtoToDo(CarDO carDO);
}
生成的代码当中包含@Component注解,可以使用@Autowired方式注入使用。
@Component
public class CarMapperImpl implements CarMapper {
@Override
public CarDTO dtoToDo(CarDO carDO) {
if ( carDO == null ) {
return null;
}
CarDTO carDTO = new CarDTO();
carDTO.id = carDO.getId();
carDTO.name = carDO.getName();
return carDTO;
}
}
mapstruct在很多情况下可以自动转换类型,比如int和String类型,可以自动调用String.valueOf(int)或Integer.parseInt(String)进行转换。
例子:
public class CarMapperImpl implements CarMapper {
@Override
public CarDTO dtoToDo(CarDO carDO) {
if ( carDO == null ) {
return null;
}
String name = null;
name = carDO.getName();
CarDTO carDTO = new CarDTO( name );
if ( carDO.getId() != null ) {
carDTO.setId( carDO.getId().intValue() );
}
// 自动转换类型
if ( carDO.getPrice() != null ) {
carDTO.setPrice( Integer.parseInt( carDO.getPrice() ) );
}
return carDTO;
}
}
具体如下;
例子:
List<CarDTO> doToDtos(List<CarDO> carDOS);
生成代码
@Override
public List<CarDTO> doToDtos(List<CarDO> carDOS) {
if ( carDOS == null ) {
return null;
}
List<CarDTO> list = new ArrayList<CarDTO>( carDOS.size() );
for ( CarDO carDO : carDOS ) {
list.add( dtoToDo( carDO ) );
}
return list;
}
例子:
@Mapper
public interface SourceTargetMapper {
@MapMapping(valueDateFormat = "dd.MM.yyyy")
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
@MapMapping(valueDateFormat = "dd.MM.yyyy")
Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source);
}
生成代码:
public class SourceTargetMapperImpl implements SourceTargetMapper {
@Override
public Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source) {
if ( source == null ) {
return null;
}
Map<String, String> map = new HashMap<String, String>( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) );
for ( java.util.Map.Entry<Long, Date> entry : source.entrySet() ) {
String key = new DecimalFormat( "" ).format( entry.getKey() );
String value = new SimpleDateFormat( "dd.MM.yyyy" ).format( entry.getValue() );
map.put( key, value );
}
return map;
}
@Override
public Map<Long, Date> stringStringMapToLongDateMap(Map<String, String> source) {
if ( source == null ) {
return null;
}
Map<Long, Date> map = new HashMap<Long, Date>( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) );
for ( java.util.Map.Entry<String, String> entry : source.entrySet() ) {
Long key;
try {
key = new DecimalFormat( "" ).parse( entry.getKey() ).longValue();
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
Date value;
try {
value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() );
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
map.put( key, value );
}
return map;
}
}
@Mapper
public interface CarMapper {
Set<String> integerStreamToStringSet(Stream<Integer> integers);
List<CarDto> carsToCarDtos(Stream<Car> cars);
CarDto carToCarDto(Car car);
}
//GENERATED CODE
@Override
public Set<String> integerStreamToStringSet(Stream<Integer> integers) {
if ( integers == null ) {
return null;
}
return integers.map( integer -> String.valueOf( integer ) )
.collect( Collectors.toCollection( HashSet<String>::new ) );
}
@Override
public List<CarDto> carsToCarDtos(Stream<Car> cars) {
if ( cars == null ) {
return null;
}
return cars.map( car -> carToCarDto( car ) )
.collect( Collectors.toCollection( ArrayList<CarDto>::new ) );
}
参考文档:https://mapstruct.org/documentation/stable/reference/html/#expressions