• 用MapStruct复制Bean



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

    依赖

    <dependency>
        <groupId>org.mapstructgroupId>
        <artifactId>mapstructartifactId>
        <version>1.5.3.Finalversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    前置准备

    我们现在有三个类,分别是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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    可以看到三个类都是关于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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们建了一个接口,叫做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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    OK,我们看到这里转换的逻辑已经成功生成了,那么接下来就是在我们的代码里面调用一下:

    PersonDTO personDTO = PersonConverter.CONVERTER.DO2DTO(personDO);
    
    • 1

    通过这样的调用,我们就能完成Bean的转换。这里需要注意的是,每个类都需要实现getset方法,否则生成的逻辑里面没有赋值的代码。

    1.2 Bean批量转换

    1.1中介绍了单Bean的点对点转换,但是有的场景中,我们的Bean是装在列表里的,我们还需要手写个列表的遍历逻辑?其实MapStruct是支持批量转换的,我们在我们的Converter接口里定义如下方法:

    /**
    * PersonDOList 2 PersonDTOList
    * @param personDOList
    * @return
    */
    List<PersonDTO> DOList2DTOList(List<PersonDO> personDOList);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们看一下生成的转换逻辑:

    @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    MapStruct自动实现了遍历列表的逻辑,很方便。

    二、指定字段映射

    2.1 指定单字段映射

    我们可以看到,PersonDO里面有个属性是job,是描述职业的属性,在生成的转换逻辑里面并没有这个字段的转换,应为在PersonDTO里,职业这个属性叫做occupation。虽然是两个属性的含义是一样的,但是名称不一样,我们同样希望自动转换的时候,MapStruct能够实现这两个属性的复制。为此,我们需要显示的告诉Mapstruct转换的映射关系:

    // 映射关系注解
    @Mapping(target = "occupation", source = "job")
    PersonDTO DO2DTO(PersonDO personDO);
    
    • 1
    • 2
    • 3

    我们为上文的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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    2.2 多字段映射

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

    @Mappings({
            @Mapping(target = "occupation", source = "job"),
            @Mapping(target = "nation", source = "country")
    })
    PersonDTO DO2DTO(PersonDO personDO);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们用@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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后我们自定义转换时的映射表达式:

    @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    我们能够看到,这里的定制逻辑实际上就是把@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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出的结果为:

    PersonVO{
    	name='null', 
    	age='null', 
    	occupation='null', 
    	nation='null', 
    	height=1.78, 
    	weight=68.0, 
    	bodyMassIndex=21.461936624163616
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    成功地计算出了BMI的值。

  • 相关阅读:
    Redis(八)集群
    linux基本指令(二)
    Stable Diffusion WebUI提示词Prompts常用推荐
    【分页】常见两种SpringBoot项目中分页技巧
    JS数据类型的探究
    F5服务器负载均衡能力如何?一文了解
    ElasticSearch--解决集群健康状态是Red、Yellow的问题
    linux服务器磁盘满了怎么办
    字符串基本操作(HNU)
    uniapp中H5使用Vconsole调试
  • 原文地址:https://blog.csdn.net/qq_35878757/article/details/127690207