ITPub博客

首页 > 数据库 > 数据库开发技术 > 代码生成器MapStruct!让项目开发”简“起来

代码生成器MapStruct!让项目开发”简“起来

原创 数据库开发技术 作者:Finally_m 时间:2023-03-14 08:00:00 0 删除 编辑
关注Java架构栈,干货天天都不断哦!

老铁们,如果我们想方便地实现不同的对象属性的映射,你会怎么做?


纳尼?你只听说过BeanUtils相关的工具类?那你也太out了!!!

既然如此,老任就来给你介绍一个牛逼闪闪的代码生成器吧,绝对会让你欣喜若狂哦。


 MapStruct介绍


MapStruct是一个代码生成器,它基于配置方法的约定,极大地简化了 Java Bean类型之间属性映射的实现。应用程序通常需要在不同的对象模型之间进行映射,编写这样的映射代码是一项繁琐且容易出错的任务。MapStruct旨在通过尽可能自动化的方式来简化这项工作。


与其他映射框架相比,MapStruct在编译时,根据映射接口文件生成bean映射的实现类,这确保了高性能,使得开发人员能及时发现映射关系的错误并进行修正。


官网:https://mapstruct.org/

帮助文档:

https://mapstruct.org/documentation/stable/reference/html/


 MapStruct的基本使用


1.导入jar

<dependencies>
    <!-- mapstruct的jar -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.3.Final</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>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <!-- 用于自动生成映射关系接口的实现类 -->
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.3.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

mapstruct-processor的作用是在项目编译阶段,针对自定义的映射接口,自动生成对应的实现类。


2.需要实现映射关系的类

2.1 User类

类中设置属性,并给出getter和setter方法。

package com.qfedu.test;

public class User {
    private Integer id;
    private String name;
    private String password;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}


2.2 UserVO类

该类中的属性名和User类中的表示相同数据的属性名一致,但是该类中没有password属性。

package com.qfedu.test;

public class UserVO {

    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}


3.定义映射接口

package com.qfedu.test;

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

// 必须使用该注解修饰mapstruct的映射接口
@Mapper
public interface UserConverter {

    // 返回映射接口的实例对象
    UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);

    /**
     * 将User对象转为UserVO对象的方法
     */
    UserVO convert(User user);

}

映射接口必须使用@Mapper进行修饰,该注解用于告知生成器自动生成映射接口的实现类。


Mappers.getMapper()不是必须的,在不通过依赖注入方式使用映射接口对象时使用。


4.调用

package com.qfedu.test;

import java.time.LocalDateTime;

public class App {

    public static void main(String[] args) {
        User user = new User();
        user.setId(1);
        user.setUsername("哈哈");
        user.setAge(30);
        user.setCreateTime(LocalDateTime.now());
        // 调用方法
        UserVO userVO = UserConverter.INSTANCE.convert(user);

        // 输出属性,进行验证
        System.out.println(userVO .getId());
        System.out.println(userVO .getName());
        System.out.println(userVO .getAge());
    }
}
}

怎么样?使用起来并不复杂吧!O(∩_∩)O哈哈~


5.自动生成的实现类

MapStruct会自动生成UserConverter的实现类UserConverterImpl

代码如下所示:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.qfedu.test;

public class UserConverterImpl implements UserConverter {
    public UserConverterImpl() {
    }

    public UserVO convert(User user) {
        if (user == null) {
            return null;
        } else {
            UserVO userVO = new UserVO();
            userVO.setId(user.getId());
            userVO.setName(user.getName());
            userVO.setAge(user.getAge());
            return userVO;
        }
    }
}

老铁们,看到实现类的代码后,是不是有种恍然大悟的感觉!原来是这个样子滴!



 MapStruct进阶


如果仅是这样简单的映射关系,也体现不出MapStruct的强大。接下来,老任带着大家一起飞!!!


1.配置映射关系

理想很丰满,现实很骨感。很多时候,不同对象需要映射的属性名并不相同,这时,我们需要怎么配置映射关系呢?


我们将User类中的name属性修改为username属性,并重新生成其getter和setter方法。


1.1 修改映射接口

当目标对象的属性名和源对象的属性名不同时,我们可以使用@Mapping注解设置属性的映射关系。

package com.qfedu.test;

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

@Mapper
public interface UserConverter {

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

    @Mappings({
            @Mapping(source = "username", target = "name")
    })
    UserVO convert(User user);

}
  • source表示源对象中的属性(即User类中的属性)

  • target表示目标对象中的属性(即UserVO对象中的属性)


1.2 查看自动生成代码



2.集成lombok

实体类中写那么多getter和setter方法,也不是个事呀!为了简化开发,我们一般会使用lombok。那么,在mapstruct中如何集成lombok呢?


2.1 修改pom文件

在<annotationProcessorPaths>中增加如下配置:

<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
</path>

<!-- 如果使用lombok1.18.16版本,需要增加如下配置 -->
<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok-mapstruct-binding</artifactId>
    <version>0.2.0</version>
</path>

最终的pom文件长这个样子:

<?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.qfedu</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.3.Final</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
            <scope>provided</scope>
        </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>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.5.3.Final</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.16</version>
                        </path>

                        <!-- additional annotation processor required as of Lombok 1.18.16 -->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok-mapstruct-binding</artifactId>
                            <version>0.2.0</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


2.2 修改实体类

实体类中删除getter和setter方法,使用@Data注解修饰类。好了,大功告成!!!

package com.qfedu.test;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class UserVO {

    private Integer id;
    private String name;
    private Integer age;
}
package com.qfedu.test;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
}


3.映射方法上使用多个源对象参数

有些时候,我们是将多个对象的属性值赋值给另一个对象的属性,有了mapstruct,这都不叫事。


3.1 实体类定义

定义UserInfo类,类中包含用户和地址的属性:

@Data
public class UserInfo {
    private Integer id;
    private String username;
    private Integer age;
    private String province;
    private String city;
    private String detailAddress;
}

Address类如下:

@Data
public class Address {
    private String province;
    private String city;
    private String detail;
}

User类如下:

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private LocalDateTime createTime;
}


3.2 定义转换方法

package com.qfedu.test;

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

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

@Mapper
public interface UserConverter {

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

    @Mappings({
            @Mapping(source = "username", target = "name"),
            @Mapping(target = "createTime", expression = "java(convertToDate(user.getCreateTime()))")
    })
    UserVO convert(User user);

    // 将多个源对象的属性赋值给目标对象
    // 如果属性名不一致,可以通过"对象名.属性名"的语法进行映射
    @Mapping(source = "address.detail", target = "detailAddress")
    UserInfo convert(User user, Address address);
}


4.自定义类型转换

有的时候,源对象和目标对象中,需要映射的属性的类型不一致,这时,我们需要怎么处理呢?

User类中createTime类型为LocalDateTime,而UserVO中,createTime类型为Date,直接通过mapstruct进行转换时,转换结果不符合预期。


4.1 方案1

通过@Mapping的expression属性,调用进行类型转换的java代码。

package com.qfedu.test;

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

import java.time.ZoneId;
import java.util.Date;

// imports属性必须写
@Mapper(imports = {Date.class, ZoneId.class})
public interface UserConverter {

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

    // expression 中,通过java()设置需要执行的java代码
    @Mappings({
            @Mapping(source = "username", target = "name"),
            @Mapping(target = "createTime", expression = "java(Date.from(user.getCreateTime().atZone( ZoneId.systemDefault()).toInstant()))")
    })
    UserVO convert(User user);

}

由于这里用到了Date和ZoneId类,因此,需要在@Mapper注解中,通过imports属性,设置需要导入的类,否则无法自动生成映射接口的实现类。


自动生成的代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.qfedu.test;

import java.time.ZoneId;
import java.util.Date;

public class UserConverterImpl implements UserConverter {
    public UserConverterImpl() {
    }

    public UserVO convert(User user) {
        if (user == null) {
            return null;
        } else {
            UserVO userVO = new UserVO();
            userVO.setName(user.getUsername());
            userVO.setId(user.getId());
            userVO.setAge(user.getAge());
            userVO.setCreateTime(Date.from(user.getCreateTime().atZone(ZoneId.systemDefault()).toInstant()));
            return userVO;
        }
    }
}

4.2 方案2

在接口中自定义不同类型数据的转换方法,通过expression属性进行调用。

package com.qfedu.test;

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

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

@Mapper
public interface UserConverter {

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

    @Mappings({
            @Mapping(source = "username", target = "name"),
            @Mapping(target = "createTime", expression = "java(convertToDate(user.getCreateTime()))")
    })
    UserVO convert(User user);

    // 接口中自定义转换方法
    default Date convertToDate(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

}

本例中,定义了convertToDate()方法,实现LocalDateTime到Date的转换。然后,在@Mapping中,通过expression属性指定调用该方法。


自动生成的代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.qfedu.test;

public class UserConverterImpl implements UserConverter {
    public UserConverterImpl() {
    }

    public UserVO convert(User user) {
        if (user == null) {
            return null;
        } else {
            UserVO userVO = new UserVO();
            userVO.setName(user.getUsername());
            userVO.setId(user.getId());
            userVO.setAge(user.getAge());
            userVO.setCreateTime(this.convertToDate(user.getCreateTime()));
            return userVO;
        }
    }
}

怎么样?老铁们是不是要赞一个:“MapStruct真香!”

如果你还有不明白的地方,可以在评论区或私信给我留言。关注Java架构栈,干货天天都不断哦。

END


近期课程上新:
Java基础 | Javaweb基础 | Java高级框架 | 微服务架构 | TiDB分布式数据库入门最佳实践 | VUE全面剖析及前后端联动实战 | 23种设计模式 | 企业级项目实战 | Java面试宝典



原文作者:Java架构栈;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-04-13

  • 博文量
    573
  • 访问量
    243797