用MapStruct复制Bean


我们在开发的过程中,经常需要对Bean做转化,而MapStruct就是一个很好的工具,它支持默认字段映射、指定字段映射和定制化映射。

依赖

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.3.Final</version>
</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的类,但是应用在不同的层,其中的某些字段名称也有一些区别。

一、使用方法

1.1单Bean转换

首先,我们想要将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的转换。这里需要注意的是,每个类都需要实现getset方法,否则生成的逻辑里面没有赋值的代码。

1.2 Bean批量转换

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自动实现了遍历列表的逻辑,很方便。

二、指定字段映射

2.1 指定单字段映射

我们可以看到,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;
}

我们可以看到,自动生成的逻辑里面已经有了joboccupation的转换。

2.2 多字段映射

Person中,不仅职业这个属性的名称不一样,国籍这个属性也不一样。在PersonDO中叫country,在PersonDTO中叫nation。我们希望职业和国籍两个属性在转换的过程中都能被成功赋值,那么就需要用到多字段映射的能力:

@Mappings({
        @Mapping(target = "occupation", source = "job"),
        @Mapping(target = "nation", source = "country")
})
PersonDTO DO2DTO(PersonDO personDO);

我们用@Mappings注解将两个属性的映射关系都囊括在内,就能实现多字段的映射处理,生成的逻辑就不展示了。

三、自定义转化方法

PersonDOPersonDTO的转换我们搞定了,但是PersonDTOPersonVO的转换还没搞定呢。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里定义了一个新方法,用于PersonDTOPersonVO的转换,为了实现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的值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值