学习笔记 - MapStruct 映射工具

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习笔记 - MapStruct 映射工具相关的知识,希望对你有一定的参考价值。

学习笔记 - MapStruct 映射工具

简介

  • MapStruct是一个代码生成器,它基于约定优于配置的方法,极大地简化了Java bean类型之间映射的实现。
    生成的映射代码使用普通方法调用,因此快速、类型安全且易于理解。

  • 多层应用程序通常需要在不同对象模型(例如实体和dto)之间进行映射。编写这样的映射代码是一项乏味且容易出错的任务。MapStruct的目标是通过尽可能地自动化来简化这项工作。
    与其他映射框架相比,MapStruct在编译时生成bean映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。

  • MapStruct是一个插入到Java编译器中的注解处理器,可以在命令行构建(Maven, Gradle等)中使用,也可以在您首选的IDE中使用。
    MapStruct使用合理的默认值,但当涉及到配置或实现特殊行为时,它也不会妨碍你。

Maven 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jerry</groupId>
    <artifactId>mapstruct-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.20</org.projectlombok.version>
        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>$org.mapstruct.version</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>$org.projectlombok.version</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.13.1</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.mapstruct</groupId>
                                <artifactId>mapstruct-processor</artifactId>
                                <version>$org.mapstruct.version</version>
                            </path>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                                <version>$org.projectlombok.version</version>
                            </path>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok-mapstruct-binding</artifactId>
                                <version>$lombok-mapstruct-binding.version</version>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

IDEA 插件

MapStruct Support

安装插件后,将光标放这里,按智能补全热键 Alt + / 可以列出所有字段供选择。

我这里用的是 Alt + / 我自定义过。因为默认的 Ctrl + 空格 跟我们切输入法冲突了。

@Mapper

实体类 Entity

@lombok.Data
@lombok.AllArgsConstructor
public class Hero
    private Integer id;
    private String name;
    private Integer gender;
    private java.time.LocalDate createDate;
    private String createBy;

数据传输对象 DTO

@lombok.Data
public class HeroDTO 
    private String name;
    private Integer sex;

映射接口

package com.jerry;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface HeroMapper 
    HeroMapper INSTANCE = Mappers.getMapper( HeroMapper.class );

    @Mapping(source ="gender", target = "sex")
    HeroDTO hero2HeroDTO(Hero hero);

测试类

package com.jerry;

import org.junit.Test;
import java.util.Date;
public class HeroMapperTest 
    @Test
    public void hero2HeroDTO() 
        Hero hero = new Hero(111, "笑虾", 1, new Date("Thu Dec 22 00:00:00 CST 2022"), "刘大");
        HeroDTO heroDto = HeroMapper.INSTANCE.hero2HeroDTO( hero );
        System.out.println(hero);
        System.out.println(heroDto);
    

  • 输出
Hero(id=111, name=笑虾, gender=1, createDate=2022-12-21, createBy=刘大)
HeroDTO(name=笑虾, sex=1)

@Mapping 作为元注解,实现重用

我们用 @Mapping 作为元注解,自定义一个注解 @CommonMapping 来配置一些需要复用的映射关系。

实体类 Entity

@Data
@AllArgsConstructor
public class HeroA 
    private Integer id;
    private String name;
    private Integer gender;
    private java.time.LocalDate createDate;
    private String createBy;
    private String a; // 独有字段(上面几个和 Hero 一样)


@Data
@AllArgsConstructor
public class HeroB 
    private Integer id;
    private String name;
    private Integer gender;
    private java.time.LocalDate createDate;
    private String createBy;
    private String b; // 独有字段(上面几个和 Hero 一样)

数据传输对象 DTO

@Data
public class HeroADTO 
    private Integer id;
    private String name;
    private Integer sex; // 名字不同的字段
    private java.time.LocalDate createDate;
    private String user;
    private String tagA; // 名字不同的字段


@Data
public class HeroBDTO 
    private Integer id;
    private String name;
    private Integer sex; // 名字不同的字段
    private java.time.LocalDate createDate;
    private String user;
    private String tagB; // 名字不同的字段

自定义注解

package com.jerry;

import org.mapstruct.Mapping;

// 忽略
@Mapping(target = "id", ignore = true) 
// 用 LocalDate.now() 来填充目标
@Mapping(target = "createDate", expression = "java(java.time.LocalDate.now())")
// user 映射到 createBy
@Mapping(source = "user", target = "createBy")
public @interface CommonMapping 

映射接口

@Mapper
public interface HeroMapper 
    @CommonMapping
    @Mapping(source ="a", target = "tagA")
    HeroADTO a2DTO(HeroA hero);

    @CommonMapping
    @Mapping(source ="b", target = "tagB")
    HeroBDTO b2DTO(HeroB hero);
    

测试类

@Test
public void h2DTOTest()
    HeroA heroa = new HeroA(888, "格尼", 1,  LocalDate.now().plusDays(1), "关二", "AAA");
    HeroADTO heroADTO = HeroMapper.INSTANCE.a2DTO(heroa);
    System.out.println(heroa);
    System.out.println(heroADTO);

    HeroB herob = new HeroB(999, "薇儿", 0, LocalDate.now().minusDays(1), "张三", "BBB");
    HeroBDTO heroBDTO = HeroMapper.INSTANCE.b2DTO(herob);
    System.out.println(herob);
    System.out.println(heroBDTO);

  • 输出
HeroA(id=888, name=格尼, gender=1, createDate=2022-12-23, createBy=关二, a=AAA)
HeroADTO(id=null, name=格尼, sex=1, createDate=2022-12-22, user=关二, tagA=AAA)

HeroB(id=999, name=薇儿, gender=0, createDate=2022-12-21, createBy=张三, b=BBB)
HeroBDTO(id=null, name=薇儿, sex=0, createDate=2022-12-22, user=张三, tagB=BBB)

自定义映射方法

当一些特殊的需求无法由 MapStruct 生成时,可以自定义映射方法。
比如从 JDK8 开始,我们可以在接口中声明默认方法来实现:

接口

@Mapper
public interface HeroMapper 
	default HeroDTO hero2DtoCustom(Hero hero)
	    HeroDTO heroDTO = new HeroDTO();
	    heroDTO.setName("【"+ hero.getName() +"】");
	    heroDTO.setSex(hero.getGender());
	    return heroDTO;
	

@Test
public void hero2DtoCustomTest()
    Hero hero = new Hero(111, "笑虾", 1, LocalDate.now().minusDays(1), "刘大");
    HeroDTO heroDto = HeroMapper.INSTANCE.hero2DtoCustom( hero );
    System.out.println(heroDto);

HeroDTO(name=【笑虾】, sex=1)

抽象类

除了接口外,也可以用抽象类来定义。比如想声明额外的字段。

@Mapper
public abstract class HeroMapper 
	public HeroDTO hero2DtoCustom(Hero hero)
	    ...
	

更多

1. 支持嵌套关系映射

@Mapping(target = "name", source = "hero.name")
HeroDTO hero2Dto(Hero hero);

2. 映射到当前

支持将嵌套bean属性映射到当前目标

@Mapper
public interface HeroMapper 
    @Mapping( target = "属性x", source = "属性a.子属性a" )
    @Mapping( target = ".", source = "属性a" )
    @Mapping( target = ".", source = "属性b" )
	HeroDTO hero2Dto(Hero hero);

3. 更新现有目标

映射结果是更新现有目标,而非创建新实例。

@Mapper
public interface HeroMapper 
	// 用 hero 更新 heroDTO 的内容
    void updateHero2Dto(Hero hero , @MappingTarget HeroDTO heroDTO);

4. 声明反向转换的方法

想声明反向转换的方法,只需一个注解搞定

    @CommonMapping
    @Mapping(source ="a", target = "tagA")
    HeroADTO hero2Dto(HeroA hero);

    @InheritInverseConfiguration
    HeroA dto2Hero(HeroADTO heroADTO);

6. Map 转 Bean

Map 转 Bean 也是支持的

@Mapper
public interface HeroMapper 
	@Mapping(source ="性别", target = "sex") // 和前面一样,名字相同的不用管,只需要告诉它名字不同的
    Hero map2Bean(Map<String, String> map);

7. 类型转换

格式化数字

@Mapper
public interface CarMapper 

    @Mapping(source = "price", numberFormat = "$#.00")
    CarDto carToCarDto(Car car);

    @IterableMapping(numberFormat = "$#.00")
    List<String> prices(List<Integer> prices);

格式化日期

@Mapper
public interface CarMapper 

    @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
    CarDto carToCarDto(Car car);

    @IterableMapping(dateFormat = "dd.MM.yyyy")
    List<String> stringListToDateList(List<Date> dates);

7. 映射集合

List<HeroDTO> heroToHeroDtos(List<Hero> heroList);

8. 还默认值、表达式、默认表达式,等等。。。

与 Lombok 一起使用

版本匹配问题,如果版本对不上会报
Error:(11, 5) java: No property named "属性名" exists in source parameter(s). Type "类名" has no properties.

官方说MapStruct 可以和Lombok配合使用,比如 MapStruct 1.2.0.Beta1Lombok 1.16.14

如果您使用的是 Lombok 1.18.16更新版本,还需要添加 lombok-mapstruct-binding ,才能让LombokMapStruct协同工作。

<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>0.2.0</version>
</dependency>

官方还有Demo:mapstruct-lombok

另外如果项目中已经使用旧版本的MapStructLombok,解决方案是将Lombok要修改的javabeanMapStruct要处理的映射器接口放在项目的两个独立模块中。然后Lombok

以上是关于学习笔记 - MapStruct 映射工具的主要内容,如果未能解决你的问题,请参考以下文章

MapStruct 映射工具

Java实体映射工具MapStruct的使用

bean复制映射工具包mapstruct

Bean映射工具MapStruct介绍

实体映射最强工具类:MapStruct 真香!

属性映射工具——MapStruct