【SpringBoot】26 实体映射工具(MapStruct)

Gitee 仓库

https://gitee.com/Lin_DH/system

介绍

现状

为了让应用程序的代码更易于维护,通常会将项目进行分层。在《阿里巴巴 Java 开发手册》中,推荐分层如下图所示:

每层都有对应的领域模型,即不同类型的 Bean。

  • DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
  • DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
  • BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象。
  • AO(Application Object):应用对象,在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
  • VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
  • Query:数据查询对象,各层接收上层的查询请求。超过两个参数的查询封装,禁止使用 Map 类进行传输。

痛点

由于代码分层的原因,就会导致代码中有多种 Bean,如 UserVO,UserDTO,UserDO 等,并且经常发生各种 VO / DTO / DO 之间的转换。从而产生很多 vo.setUsername(dto.getUsername()) 的代码。当字段多了不仅容易出错,而且很浪费开发时间。也有使用 BeanUtils.copyProperties() 进行转换,这样虽然减少了开发时间和代码,但依然存在问题。如:1)利用反射导致性能不好;2)不同名称的属性无法直接进行映射。

解决方案

本次使用的 Java 实体对象映射框架是 MapStruct 。MapStruct基于 JSR 269 的 Java 注解处理器,用于生成类型安全,高性能,无依赖的 Bean 映射代码,自动生成对象的代码,使用便捷,性能优越。

特点

  • 1)通过 getter / setter 进行字段拷贝,而不是利用反射机制。
  • 2)字段名称相同直接转换,名称不同使用 @Mapping 注解标识。

区别

与动态映射框架相比,MapStruct 的优势:

  • 1)使用普通的 getter / setter 方法,而不是反射机制,执行更快,性能更好。
  • 2)编译时类型安全。
  • 3)清晰的错误提示信息。

依赖

pom.xml

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

xml 复制代码
<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>

代码实现

第一步:编写 Student 实体类

Student.java

java 复制代码
package com.lm.system.common;

import lombok.*;

import java.io.Serializable;
import java.util.Date;

/**
 * @author DUHAOLIN
 * @date 2024/11/12
 */
@Data
@Builder
public class Student implements Serializable {

    private static final long serialVersionUID = 1L;
    private Integer id;
    private String name;
    private Integer age;
    private String gender;
    private Date createTime;

}

第二步:编写 StudentVO 实体类

StudentVO.java

java 复制代码
package com.lm.system.common.dto;

import lombok.Data;

/**
 * @author DUHAOLIN
 * @date 2024/11/12
 */
@Data
public class StudentVO {

    private Integer userId;
    private String username;
    private Integer age;
    private String gender;

}

第三步:编写实体类转换接口

StudentConvert.java

java 复制代码
package com.lm.system.convert;

import com.lm.system.common.Student;
import com.lm.system.common.dto.StudentVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * @author DUHAOLIN
 * @date 2024/11/12
 */
@Mapper
public interface StudentConvert {

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

    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "name", target = "username")
    })
    StudentVO toStudentVO(Student student);

}

第四步:编写测试类

MapStructTest.java

java 复制代码
package com.lm.system.test;

import com.lm.system.common.Student;
import com.lm.system.common.dto.StudentVO;
import com.lm.system.convert.StudentConvert;
import org.junit.Test;

import java.util.Date;

/**
 * @author DUHAOLIN
 * @date 2024/11/12
 */
public class MapStructTest {

    @Test
    public void testStudent() {
        Student student = getStudent();
        System.out.println(student);
        StudentVO studentVO = StudentConvert.INSTANCES.toStudentVO(student);
        System.out.println(studentVO);
    }

    private Student getStudent() {
        return Student.builder()
                .id(1)
                .name("Tom")
                .age(18)
                .gender("男")
                .createTime(new Date())
                .build();
    }

}

效果图

属性处理

简单属性

当 gender 传入的是男或女,需要转换成对应的0或1,再传入数据库时,则需要进行处理。

StudentConvert.java

java 复制代码
@Mappings({
        @Mapping(source = "id", target = "userId"),
        @Mapping(source = "name", target = "username"),
        @Mapping(target = "gender", expression = "java(student.getGender() == \"男\" ? \"0\" : \"1\")")
})
StudentVO toStudentVO(Student student);

复杂属性

限制输入年龄的数值。

StudentConvert.java

java 复制代码
package com.lm.system.convert;

import com.lm.system.common.Student;
import com.lm.system.common.dto.StudentVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;

/**
 * @author DUHAOLIN
 * @date 2024/11/12
 */
@Mapper
public interface StudentConvert {

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

    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "name", target = "username"),
            @Mapping(target = "gender", expression = "java(student.getGender() == \"男\" ? \"0\" : \"1\")"),
            @Mapping(source = "age", target = "age", qualifiedByName = "transferAge")
    })
    StudentVO toStudentVO(Student student);

    @Named("transferAge")
    default Integer transferAge(Integer age) {
        if (age < 0) {
            return 0;
        }
        else if (age > 120){
            return 120;
        }
        else {
            return age;
        }
    }

}

Spring中使用

如果在 Spring 中使用,需要修改组件模型为 spring,可以通过 pom.xml 参数修改,也可以通过注解修改。修改后会在实现类上添加 @Component 注解,从而成为一个 Bean,加入 Spring 容器中。

StudentConvert.java

java 复制代码
@Mapper(componentModel = "spring")
public interface StudentConvert {}

报错

如果遇到报错:java.lang.NoSuchMethodError,则在 IDEA 右侧的 Maven 选项中,运行 clean 和 compile,再进行重试。

项目结构图

参考链接

推荐一款Java实体映射工具---mapstruct:【https://www.cnblogs.com/lvmengtian/p/14594185.html】

【springboot进阶】优雅使用 MapStruct 进行类复制:【https://blog.csdn.net/lrb0677/article/details/127838138】

芋道 Spring Boot 对象转换 MapStruct 入门:【https://www.iocoder.cn/Spring-Boot/MapStruct/?self】

相关推荐
猿来入此小猿4 分钟前
基于SpringBoot在线音乐系统平台功能实现十七
java·spring boot·后端·毕业设计·音乐系统·音乐平台·毕业源码
繁川5 分钟前
Spring Boot缓存预热实战指南
spring boot·spring·缓存
犬余5 分钟前
《Java源力物语》-4.集合府邸的新秀
java·开发语言
重整旗鼓~35 分钟前
2.flask中使用装饰器统一验证用户登录
后端·python·flask
苹果醋340 分钟前
Vue3响应式数据: 深入分析Ref与Reactive
java·运维·spring boot·mysql·nginx
缘友一世1 小时前
JAVA代理模式和适配器模式
java·代理模式·适配器模式
轻浮j1 小时前
Sentinel底层原理以及使用算法
java·算法·sentinel
it噩梦1 小时前
springboot 工程使用proguard混淆
java·spring boot·后端
潜意识起点1 小时前
Java数组:静态初始化与动态初始化详解
java·开发语言·python
竹影卿心1 小时前
Java连接HANA数据库
java·数据库·windows