其他分享
首页 > 其他分享> > mapstruct常见用法梳理

mapstruct常见用法梳理

作者:互联网

目录

一、前言

二、mapstruct使用前的准备

1、maven方式

2、Gradle方式

3、 Apache Ant方式

三、mapstruct使用方式梳理

1、最简单的映射

2、不同属性名映射

3、不同类型映射

        1)、日期格式转化        

        2)、数字格式转化

4、List映射 

5、Map和Set映射

6、枚举映射

四、总结

参考


一、前言

        在互联网企业中,随着业务越来越繁琐,导致系统架构越来越复杂,很多企业都使用DDD对系统进行拆分。DDD通常分很多层次,每个层次都有相应的逻辑,在不同层次之间进行调用时,难免需要对实体类进行转换,例如将UserDo转化成UserDto。目前业界有很多种对象映射工具,例如:

  1. json方式。将UserDo转化成json字符串后,再通过fastjson的JSON.parseObject方法将json字符串转化成UserDto类。
  2. BeanUtils.copyProperties()。BeanUtils.copyProperties()比较有名的有Apache的BeanUtils和Spring的BeanUtils。虽然两者性能上有所差异,但是都是通过反射实现的,总体性能还是欠佳。
  3. BeanCopier。BeanCopier通过字节码方式转换成性能最好的get和set方式,会动态生成一个被代理的类的子类,总体性能比BeanUtils好。
  4. 其他编译器转化工具。还有很多在编译期间进行转化的工具,例如MapStruct,Selma,Orika等。

        本文主要介绍mapstruct的常用方式。mapstruct是一个实现JSR269的bean映射工具。只需定义一个 mapper接口,该接口声明添加@Mapper注解,然后再mapper接口中定义一个转化方法。mapstruct就能在编译期间将一个实体类的属性值映射到另一个实体类中。本文只梳理mapstruct的一些常见用法,具体原理可以参考mapstruct原理解析

二、mapstruct使用前的准备

        mapstruct使用前需要在项目中引入对应的依赖。

1、maven方式

        如果是maven,则使用下列方式引入。

<properties>
    <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source> <!-- depending on your project -->
                <target>1.8</target> <!-- depending on your project -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2、Gradle方式

        如果Gradle版本>= 4.6,需要添加下列代码到build.gradle中

dependencies {
    ...
    implementation 'org.mapstruct:mapstruct:1.4.2.Final'
 
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}

         如果Gradle版本 <4.6,需要引入下列代码到build.gradle中。

plugins {
    ...
    id 'net.ltgt.apt' version '0.21'
}
dependencies {
    ...
    compile 'org.mapstruct:mapstruct:1.4.2.Final'
 
    apt 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}

3、 Apache Ant方式

        如果使用Apache Ant,则需要在build.xml中引入下列代码。

...
<javac
    srcdir="src/main/java"
    destdir="target/classes"
    classpath="path/to/mapstruct-1.4.2.Final.jar">
    <compilerarg line="-processorpath path/to/mapstruct-processor-1.4.2.Final.jar"/>
    <compilerarg line="-s target/generated-sources"/>
</javac>
...

三、mapstruct使用方式梳理

        mapstruct使用方式比较多,还可以支持比较复杂的操作。下面依次列举常见的使用方式。

1、最简单的映射

        最简单的映射就是两个实体类的属性名和属性类型完全一一对应,这种方式最简单。为了直观起见,使用两个实体类进行说明。

public class UserDto {
    private int id;
    private String name;
}

public class UserDO {
    private int id;
    private String name;
}

        为了实现UserDto和UserDO之间实现映射,需要定义一个接口:

@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    UserDto toDto(UserDo userDo);
}

         上述UserMapper接口在编译期间会被mapstruct创建一个实现类UserMapperImpl,然后实现toDto方法。实现类如下代码所示:

public class UserMapperImpl implements UserMapper {
    @Override
    public UserDto toDto(UserDo userDo) {
        if (userDo == null) {
            return null;
        }

        UserDto userDto = new UserDto();
        if ( userDo.getId() != null ) {
            userDto.setId( userDo.getId() );
        }

        if ( userDo.getName() != null ) {
            userDto.setUserName( userDo.getName() );
        }
        return userDto    
}

2、不同属性名映射

        很多场景两个映射实体直接的属性名可能不同,但是却需要将他们的值进行映射,老规矩还是先列出两个映射实体,再介绍使用方式。

public class UserDto {
    private int id;
    private String name;
}


public class UserDo {
    private int id;
    private String userName;
}

         上述UserDto的name和UserDo的userName两个属性名不同。为了让mapstruct实现他们之间的映射,需要在方法上显示的标记。具体实现如下代码所示:

@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "userName", target = "name")
    UserDto toDto(UserDo userDo);
}

        @Mapping注解能够将UserDo中的userName对应到UserDto中的name字段。

        如果UserDo和UserDto有多个属性名不同,则需要使用@Mappings注解包裹@Mapping,假设UserDo和UserDto代码如下: 

public class UserDto {
    private int id;
    private String name;
    private int age;
}


public class UserDo {
    private int id;
    private String userName;
    private int userAge;
}

        需要在toDto方法上加上@Mappings注解。

@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mappings({
        @Mapping(source = "userName", target = "name"),
        @Mapping(source = "userAge", target = "age")
    })
    UserDto toDto(UserDo userDo);
}

3、不同类型映射

        1)、日期格式转化        

        mapstruct支持日期类型与其他类型的转化。如将LocalDate格式映射成Sting,可以使用如下方式:

public class UserDto {
    private int id;
    private String name;
    private int age;
    private String birthday;
}

public class UserDo {
    private int id;
    private String name;
    private int age;
    private LocalDate birthday;
}


@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "birthday", target = "birthday", dateFormat = "dd/MMM/yyyy")
    UserDto toDto(UserDo userDo);
}

        上述的@Mapping里添加了dateFormat,mapstruct使用下列代码将birthday转化成String类型。

DateTimeFormatter.ofPattern("dd/MMM/yyyy") .format(UserDo.getBirthday())

        2)、数字格式转化

        数字格式的转化,可以使用numberFormat进行转换。代码如下所示:

public class UserDto {
    private int id;
    private String name;
    private int age;
    private String wealth;
}

public class UserDo {
    private int id;
    private String name;
    private int age;
    private int wealth;
}


@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "wealth", target = "wealth", numberFormat = "$#.00")
    UserDto toDto(UserDo userDo);
}

4、List映射 

        mapstruct支持List的映射,使用List转化是需要有单个实体之间的转化方法,具体代码如下:

@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "wealth", target = "wealth", numberFormat = "$#.00")
    UserDto toDto(UserDo userDo);

    List<UserDto> toDtos(List<UserDo> userDoList);
}

            注意,假如UserDto中有个字段在UserDo中不存在,在生成代码时会报错。因此需要在toDto中添加@Mapping注解,将不存在的字段进行特殊映射或者直接忽略掉。代码如下所示:

public class UserDto {
    private int id;
    private String name;
    private int age;
    private String wealth;
}

public class UserDo {
    private int id;
    private String name;
    private int age;
}


@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(target = "wealth", ignore = true)
    UserDto toDto(UserDo userDo);

    List<UserDto> toDtos(List<UserDo> userDoList);
}

5、Map和Set映射

        Map和Set的映射与List类似,具体示例如下:

public class UserDto {
    private int id;
    private String name;
    private int age;
    private String wealth;
}

public class UserDo {
    private int id;
    private String name;
    private int age;
}


@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(target = "wealth", ignore = true)
    UserDto toDto(UserDo userDo);

    List<UserDto> toDtos(List<UserDo> userDoList);

    Set<UserDto> toDtoSet(Set<UserDo> userDoSet);

    Map<Long, UserDto> toDtoMap(Map<Long, UserDo> userDoMap);
}

6、枚举映射

       枚举映射与实体类的字段映射类似,mapstruct会根据相同的名称自动进行映射。不过如果字段名称不同,则需要使用@ValueMapping注解。代码示例如下:

public enum ExampleMappingEnum {
    ENUM1,
    ENUM2,
    ENUM_MAPPING3,
    ENUM_MAPPING4
}

public enum ExampleEnum {
    ENUM1,
    ENUM2,
    ENUM3
}

@Mapper
public interface ExampleEnumMapper {

    ExampleEnumMapper INSTANCE = Mappers.getMapper(ExampleEnumMapper.class);

    @ValueMappings({
            @ValueMapping(source = "ENUM_MAPPING3", target = "ENUM3"),
            @ValueMapping(source = "ENUM_MAPPING4", target = "ENUM3")
    })
    ExampleEnum map(ExampleMappingEnum exampleMappingEnum);

}

        上述代码的ExampleMappingEnum比ExampleEnum多一个值,并且枚举值也不同,需要通过@ValueMapping进行转化,mapstruct编译后的代码如下:

public class ExampleEnumMapperImpl implements ExampleEnumMapper {

    @Override
    public ExampleEnum map(ExampleMappingEnum exampleMappingEnum) {
        if (exampleMappingEnum == null) {
            return null;
        }

        ExampleEnum exampleEnum;

        switch (exampleMappingEnum) {
            case ENUM_MAPPING3: exampleEnum = ExampleEnum.ENUM3;
            break;
            case ENUM_MAPPING4: exampleEnum = ExampleEnum.ENUM3;
            break;
            case ENUM1: exampleEnum = ExampleEnum.ENUM1;
            break;
            case ENUM2: exampleEnum = ExampleEnum.ENUM2;
            break;
            default: throw new IllegalArgumentException( "Unexpected enum constant: " + exampleMappingEnum );
        }
        return exampleEnum;
    }
}

         假如不想像上述那样指定某几个枚举值映射到特定的枚举值,也可以直接使用MappingConstants进行默认映射。

@Mapper
public interface ExampleEnumMapper {

    ExampleEnumMapper INSTANCE = Mappers.getMapper(ExampleEnumMapper.class);

    @@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "ENUM3")
    ExampleEnum map(ExampleMappingEnum exampleMappingEnum);

}

        通过MappingConstants,mapstruct会将所有未映射的值映射到ENUM3中。

四、总结

        本文主要讲解了mapstruct比较常见的使用方式。在最开始简单比较了下目前行业中的一些映射工具,然后介绍了下mapstruct的使用前准备工作,最后梳理了mapstruct一些比较常规的使用方式。当然mapstruct还有很多高级的用法,限于篇幅,就暂时不展开,可以参考mapstruct官网

参考

 

Installation – MapStructhttps://mapstruct.org/documentation/installation/

掘金https://juejin.cn/post/6992399204760944647

标签:UserDto,映射,mapstruct,UserMapper,private,梳理,用法,public
来源: https://blog.csdn.net/datastructure18/article/details/120400228