mapstruct高级使用方法

原文地址: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依赖

<dependency>
  <groupId>org.mapstruct</groupId>
  <!-- jdk8以下就使用mapstruct -->
  <artifactId>mapstruct-jdk8</artifactId>
  <version>1.3.1.Final</version>
</dependency>
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct-processor</artifactId>
  <version>1.3.1.Final</version>
</dependency>

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.20</version>
</dependency>

为了省懒,也使用了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
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值