【springboot进阶】优雅使用 MapStruct 进行类复制

导读:本篇文章讲解 【springboot进阶】优雅使用 MapStruct 进行类复制,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

目录

 一、MapStruct 介绍

二、MapStruct 配置

三、MapStruct 使用

四、测试

五、遇到的坑

1、java.lang.NoSuchMethodError


项目中经常会遇到这样的一个情况:从数据库读取到数据,并不是直接返回给前端做展示的,还需要字段的加工,例如记录的时间戳是不需要的、一些敏感数据更是不能等等。传统的做法就是创建一个新的类,然后写一堆的get/set方法进行赋值,如果字段很多的话,那简直是噩梦,有时候还担心会漏掉等等。

 一、MapStruct 介绍

MapStruct 简单来说就是一个属性映射工具,主要用于解决数据模型之间不通用的情况。这里主要说一下它的优点在于性能好,像笔者在未接触 MapStruct 以前一直使用的 BeanUtils 工具进行转换,当时也知道这样性能不好,但是为了能偷懒,所以…

其实 MapStruct 也不是有神秘之处,其实原理在于,Java程序执行的过程,是由编译器先把java文件编译成class字节码文件,然后由JVM去解释执行class文件。Mapstruct正是在java文件到class这一步帮我们实现了转换方法,即做了预处理,提前编译好文件。

二、MapStruct 配置

需要引入 mapstruct 和 mapstruct-processor,同时 scope 设置为 provided ,即它只影响到编译,测试阶段。

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.0.Final</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.0.Final</version>
    <scope>provided</scope>
</dependency>

三、MapStruct 使用

这边演示的是一般项目中,从数据库读取到数据,到返回前端展示的过程。

 假设我们有一个student表,实体字段信息如下。

/**
 * <p>
 * 学生表
 * </p>
 *
 * @author Liurb
 * @since 2022-11-13
 */
@Getter
@Setter
@TableName("demo_student")
public class Student implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 学生名称
     */
    @TableField("`name`")
    private String name;

    /**
     * 学生年龄
     */
    @TableField("age")
    private Integer age;

    /**
     * 学生性别
     */
    @TableField("sex")
    private String sex;

    /**
     * 创建时间
     */
    @TableField("created_at")
    private LocalDateTime createdAt;


}

但是前端页面展示的时候,某些字段需要调整。例如,学生信息需要展示在首页和列表页,他们的数据模型字段名称是不一致的。

学生首页展示vo 需要调整学生的 id 为 userId, 学生名称为 userName 。

/**
 * 学生首页展示vo
 *
 *
 * @Author Liurb
 * @Date 2022/11/13
 */
@Data
public class StudentHomeVo {

    private Integer userId;

    private String userName;

    private Integer age;

    private String sex;

}

学生分页展示vo 需要调整学生的性别为 gender 。 

/**
 * 学生分页展示vo
 *
 *
 * @Author Liurb
 * @Date 2022/11/13
 */
@Data
public class StudentPageVo {

    private Integer id;

    private String name;

    private Integer age;

    private String gender;

}

创建 学生实体的mapper,由于要区分 mybatis-plus 的底层mapper,所以这里的命名以 StructMapper 结尾,尽量避免重名的情况。所以注意 @Mapper 注解也要使用 org.mapstruct 包下的。

/**
 * 学生实体转换接口
 *
 * 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
 *
 * @Author Liurb
 * @Date 2022/11/13
 */
@Mapper
public interface StudentStructMapper {

    /**
     * 获取该类自动生成的实现类的实例
     *
     */
    StudentStructMapper INSTANCES = Mappers.getMapper(StudentStructMapper.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 注解 用于定义属性复制规则
     * source 指定源对象属性
     * target指定目标对象属性
     *
     * @param student 这个参数就是源对象,也就是需要被复制的对象
     * @return 返回的是目标对象,就是最终的结果对象
     */
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "name", target = "userName")
    })
    StudentHomeVo toStudentHomeVo(Student student);

    /**
     * 也可以实现多个复制方法,一般将一个实体源对象的转换写在一起
     *
     * @param student
     * @return
     */
    @Mapping(source = "sex", target = "gender")
    StudentPageVo toStudentPageVo(Student student);
}

四、测试

我们创建一个controller,模拟一般项目的接口请求。

/**
 * mapstruct实例控制器
 *
 * @Author Liurb
 * @Date 2022/11/13
 */
@RestController
@RequestMapping("/demo_api/mapstruct")
public class MapStructController {

    @Resource
    StudentService studentService;

    @GetMapping("/home/{id}")
    public StudentHomeVo home(@PathVariable("id")Integer id) {

        Student student = studentService.getById(id);

        StudentHomeVo studentHomeVo = StudentStructMapper.INSTANCES.toStudentHomeVo(student);

        return studentHomeVo;
    }

    @GetMapping("/page")
    public List<StudentPageVo> page() {
        List<Student> students = studentService.list();

        List<StudentPageVo> studentPageVos = students.stream().map(item -> {

            StudentPageVo studentPageVo = StudentStructMapper.INSTANCES.toStudentPageVo(item);
            return studentPageVo;
        }).collect(Collectors.toList());

        return studentPageVos;
    }

}

数据表的记录如下

【springboot进阶】优雅使用 MapStruct 进行类复制

调用首页展示接口的情况如下,可以看到,返回的新字段已经成功赋值。

【springboot进阶】优雅使用 MapStruct 进行类复制

 接下来,看一下分页的数据,新字段 性别 gender 也同样赋值成功。

【springboot进阶】优雅使用 MapStruct 进行类复制

五、遇到的坑

1、java.lang.NoSuchMethodError

如果现在我们将学生首页vo类的 age 字段,调整为 userAge,运行项目,在请求一次接口,你会发现这时候会报错,提示找不到 setAge 方法。

【springboot进阶】优雅使用 MapStruct 进行类复制

为什么会这样呢?其实原因在于上面说的 MapStruct 工作原理,这时候查看转换接口的实现就可以知道是什么情况了。

【springboot进阶】优雅使用 MapStruct 进行类复制

【springboot进阶】优雅使用 MapStruct 进行类复制

 实现类还是调用的 setAge 方法进行赋值,但是我们的 StudentHomeVo 已经被我们改过,没有这个方法了,所以运行时候就会报错了。

那么这种情况如何解决了,其实也很简单,重新编译一次项目就可以了。

【springboot进阶】优雅使用 MapStruct 进行类复制

 重新编译运行,再请求一次接口,可以看到成功返回,并且新字段也有赋值。 

【springboot进阶】优雅使用 MapStruct 进行类复制

如果发现调整了字段,或者改过转换mapper的东西后,出现奇奇怪怪的情况,一种重新编译一下项目就能解决。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/93596.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!