mapstruct高级使用方法

  • Post author:
  • Post category:其他


原文地址:https://mp.weixin.qq.com/s/3PjpKKcVifqYluUpXYsyrA

背景介绍

在微服务盛行的当下,相信对领域驱动设计都不陌生,在领域驱动设计中,我们经常要处理将DTO转换成Domain,Domain转成Entity等各类对象相互转换,在没有接触mapstruct之前,相信大多数人都是使用Spring框架自带的BeanUtils或者直接使用getter/setter方法进行属性赋值,如果我们采用BeanUtils工具类的copyProperty进行转换,除了性能低之外(因为是利用反射实现的),还有可能出现类型转换错误等问题。而使用getter/setter方法需要写很多代码,效率低。

与BeanUtils转换工具相比,MapStruct具有以下优点:

1.使用纯Java方法代替Java反射机制快速执行

2.编译时类型安全:只能映射彼此的对象和属性

3.如果无法映射实体或属性,则在编译时清除错误报告

图片

从上图可以看出性能优势比较明显。

Maven依赖

org.mapstruct
mapstruct-jdk8 1.3.1.Final org.mapstruct mapstruct-processor 1.3.1.Final org.projectlombok lombok 1.16.20 为了省懒,也使用了lombok,所以也加入了lombok的依赖,需要注意的是lombok版本,如果使用了lombok的1.16.10版本,需要调整为1.16.20版本,否则在编译的时候可能会报无参构造函数等等错误。

使用方法

下面使用Staff和StaffEntity对象互相转换为例进行说明mapstruct的使用方法,基本包含了各种情况下的使用方法。

由于一个项目内会有很多这种对象间转换的情况,下面我们抽象出一个转换基类,不同对象如果只是简单转换则可以直接继承该基类,不需要覆写基类的任何方法,即只需要一个空类就可以。需要说明的是如果子类覆写了基类的方法,那么基类上配置的 @Mapping 就会失效。

package com.ntmy.demo.common;
 
import org.mapstruct.InheritConfiguration;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.MapperConfig;
 
import java.util.List;
import java.util.stream.Stream;
 
/**
 * @author Administrator
 */
@MapperConfig
public interface BaseMapping<S, T> {
 
    /**
     * 映射同名属性
     * @param source
     * @return
     */
    T source2target(S source);
 
    /**
     * 反向映射同名属性
     * @param target
     * @return
     */
    @InheritInverseConfiguration(name = "source2target")
    S target2Source(T target);
 
    /**
     * 集合形式的映射同名属性
     * @param source
     * @return
     */
    @InheritConfiguration(name = "source2target")
    List<T> source2target(List<S> source);
 
    /**
     * 集合形式的反向映射同名属性
     * @param target
     * @return
     */
    @InheritConfiguration(name = "target2Source")
    List<S> target2Source(List<T> target);
 
    /**
     * 集合流形式的映射同名属性
     * @param source
     * @return
     */
    List<T> source2target(Stream<S> source);
 
    /**
     * 集合流形式的反向映射同名属性
     * @param target
     * @return
     */
    List<S> target2Source(Stream<T> target);
}

创建Staff与StaffEntity对象的转换器

package com.ntmy.demo.translator;
 
import com.ntmy.demo.common.BaseMapping;
import com.ntmy.demo.domain.Staff;
import com.ntmy.demo.entity.StaffEntity;
import com.ntmy.demo.enums.StatusEnum;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
 
import java.util.Objects;
 
/**
 * @author Administrator
 */
@Mapper(componentModel = "spring")
public interface StaffTranslator extends BaseMapping<Staff, StaffEntity> {
 
    @Override
    @Mappings({
            @Mapping(source = "description", target = "staffDescription")
,
            @Mapping(source = "department.id", target = "departmentId"),
            @Mapping(target = "birthday", dateFormat = "yyyyMMdd"),
            @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss"),
            @Mapping(target = "salary", numberFormat = "0.00")
    })
    StaffEntity source2target(Staff source);
 
 
    @Override
    @Mappings({
            @Mapping(source = "staffDescription", target = "description")
,
            @Mapping(source = "departmentId", target = "department.id"),
            @Mapping(target = "password", ignore = true),
            @Mapping(target = "birthday", dateFormat = "yyyyMMdd"),
            @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    })
    Staff target2Source(StaffEntity target);
 
 
    default Integer statusEnum2Int(StatusEnum value){
        return Objects.isNull(value) ? null : value.getValue();
    }
 
    default StatusEnum int2StatusEnum(Integer value){
        return Objects.isNull(value) ? null : StatusEnum.getByValue(value);
    }
}

说明:

@Mapping(source = “description”, target = “staffDescription”)

将Staff对象的description属性映射StaffEntity对象的staffDescription属性,在属性类型一样的情况。

@Mapping(source = “department.id”, target = “departmentId”)

将Staff对象的department对象的id属性映射StaffEntity对象的departmentId属性,在属性类型不一样的情况。

@Mapping(target = “birthday”, dateFormat = “yyyyMMdd”)



@Mapping(target = “createTime”, dateFormat = “yyyy-MM-dd HH:mm:ss”)

将Staff对象的birthday属性映射StaffEntity对象的birthday属性时对日期按照dateFormat配置的格式进行格式化,在属性类型不一样的情况,针对的是日期和字符串类型之间的转换。

@Mapping(target = “salary”, numberFormat = “0.00”)

将Staff对象的salary属性映射StaffEntity对象的salary属性时对数值按照numberFormat配置的格式进行格式化,在属性类型不一样的情况,这个是对数值进行格式化,只是这个格式化针对的是数值和字符串之间的转换。

@Mapping(target = “password”, ignore = true)

在StaffEntity转换成Staff对象时会忽略掉password这个属性。

default Integer statusEnum2Int(StatusEnum value){
    return Objects.isNull(value) ? null : value.getValue();
}

default StatusEnum int2StatusEnum(Integer value){
    return Objects.isNull(value) ? null : StatusEnum.getByValue(value);
}

这个是自定义的数据类型转换,将StatusEnum枚举类型转换成int类型

下面看看上面的配置编译之后的代码(可以到项目的target/generated-sources/annotations下面找到)。

1.Staff转StaffEntity时编译后的代码

@Override
public StaffEntity source2target(Staff source) {
    if ( source == null ) {
        return null;
    }

    StaffEntity staffEntity = new StaffEntity();

    staffEntity.setStaffDescription( source.getDescription() );
    staffEntity.setDepartmentId( sourceDepartmentId( source ) );
    staffEntity.setId( source.getId() );
    staffEntity.setName( source.getName() );
    staffEntity.setPassword( source.getPassword() );
    staffEntity.setSex( source.getSex() );
    if ( source.getBirthday() != null ) {
        staffEntity.setBirthday( new SimpleDateFormat( "yyyyMMdd" ).format( source.getBirthday() ) );
    }
    if ( source.getSalary() != null ) {
        staffEntity.setSalary( new DecimalFormat( "0.00" ).format( source.getSalary() ) );
    }
    staffEntity.setStatus( statusEnum2Int( source.getStatus() ) );
    if ( source.getCreateTime() != null ) {
        staffEntity.setCreateTime( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( source.getCreateTime() ) );
    }

    return staffEntity;
}

2.StaffEntity转Staff时编译后的代码

@Override
public Staff target2Source(StaffEntity target) {
    if ( target == null ) {
        return null;
    }

    Staff staff = new Staff();

    staff.setDepartment( staffEntityToDepartment( target ) );
    staff.setDescription( target.getStaffDescription() );
    staff.setId( target.getId() );
    staff.setName( target.getName() );
    staff.setSex( target.getSex() );
    try {
        if ( target.getBirthday() != null ) {
            staff.setBirthday( new SimpleDateFormat( "yyyyMMdd" ).parse( target.getBirthday() ) );
        }
    }
    catch ( ParseException e ) {
        throw new RuntimeException( e );
    }
    if ( target.getSalary() != null ) {
        staff.setSalary( Double.parseDouble( target.getSalary() ) );
    }
    staff.setStatus( int2StatusEnum( target.getStatus() ) );
    try {
        if ( target.getCreateTime() != null ) {
            staff.setCreateTime( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).parse( target.getCreateTime() ) );
        }
    }
    catch ( ParseException e ) {
        throw new RuntimeException( e );
    }

    return staff;
}

结果验证

1.验证Staff转StaffEntity

@Test
public void testStaff2StaffEntity(){

    Department department = new Department().setId(666).setName("研发部");

    Staff staff = new Staff()
            .setId(1)
            .setName("奈斯兔米特优")
            .setDescription("想不出该怎么来描述")
            .setBirthday(new Date())
            .setSex(1)
            .setSalary(88888.88888)
            .setPassword("1234567890")
            .setStatus(StatusEnum.ENABLED)
            .setDepartment(department)
            .setCreateTime(new Date());

    StaffEntity entity = staffTranslator.source2target(staff);

    log.info("Staff: {}", staff.toString());
    log.info("StaffEntity: {}", entity.toString());
}

结果:

Staff: Staff(id=1, name=奈斯兔米特优, password=1234567890, sex=1, birthday=Sun Jun 20 11:03:47 CST 2021, salary=88888.88888, status=ENABLED, description=想不出该怎么来描述, department=Department(id=666, name=研发部), createTime=Sun Jun 20 11:03:47 CST 2021)

StaffEntity: StaffEntity(id=1, name=奈斯兔米特优, password=1234567890, sex=1, birthday=20210620, salary=88888.89, status=1, staffDescription=想不出该怎么来描述, departmentId=666, createTime=2021-06-20 11:03:47)

2.验证StaffEntity转Staff

@Test
public void testStaffEntityStaff(){

    StaffEntity entity = new StaffEntity()
            .setId(1)
            .setName("奈斯兔米特优")
            .setStaffDescription("想不出该怎么来描述")
            .setBirthday("20210613")
            .setSex(1)
            .setSalary("88888.88888")
            .setPassword("1234567890")
            .setStatus(StatusEnum.ENABLED.getValue())
            .setDepartmentId(666)
            .setCreateTime("2021-06-20 11:03:47");

    Staff staff = staffTranslator.target2Source(entity);
    log.info("StaffEntity: {}", entity.toString());
    log.info("Staff: {}", staff.toString());
}

结果:

StaffEntity: StaffEntity(id=1, name=奈斯兔米特优, password=1234567890, sex=1, birthday=20210613, salary=88888.88888, status=1, staffDescription=想不出该怎么来描述, departmentId=666, createTime=2021-06-20 11:03:47)

Staff: Staff(id=1, name=奈斯兔米特优, password=null, sex=1, birthday=Sun Jun 13 00:00:00 CST 2021, salary=88888.88888, status=ENABLED, description=想不出该怎么来描述, department=Department(id=666, name=null), createTime=Sun Jun 20 11:03:47 CST 2021)

测试代码工程仓库

https://gitee.com/nice_h/mapstruct-demo