可别再用BeanUtils了(性能拉胯),试试这款转换神器

老铁们是不是经常为写一些实体转换的原始代码感到头疼,尤其是实体字段特别多的时候。有的人会说,我直接使用get/set方法。没错,get/set方法的确可以解决,而且也是性能较高的处理方法,但是大家有没有想过,要是转换的实体字段比较多,那么get/set方法的代码量是非常恐怖的;有人会说,用自带的BeanUtils工具类,但在属性拷贝的过程中,会遇到类型转换异常,或是影响性能等等问题。所以,我把自己项目中使用的这款转换神器分享出来。BeanUtils属性复制

之前对各种属性映射工具的性能进行了简单的对比,结果如下:

56663cf641104ea29416754059302952.png

先贴下官网地址吧:https://mapstruct.org/

一、pom 配置

    <!-- 属性设置 -->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <mybatisPlus.version>3.5.3.1</mybatisPlus.version>
        <lombok.version>1.18.26</lombok.version>
        <mysql.version>8.0.30</mysql.version>
        <alibaba.druid.version>1.2.4</alibaba.druid.version>
        <thymeleaf.version>3.0.15.RELEASE</thymeleaf.version>
        <spring4.version>3.0.12.RELEASE</spring4.version>
        <mapstruct.version>1.5.3.Final</mapstruct.version>
    </properties>

    <!-- 依赖关系 -->
    <dependencies>
        <!-- springmvc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 测试test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- jpa(持久层) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--mybatis-plus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisPlus.version}</version>
        </dependency>
        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

        <!-- mysql(数据库) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- alibaba数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${alibaba.druid.version}</version>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>${thymeleaf.version}</version>
        </dependency>

        <!-- Spring 4集成包(这可能是Spring应用程序所需的一切)-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring4</artifactId>
            <version>${spring4.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 属性转换 -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>

        <!-- idea 2018.1.1 之前的版本需要添加下面的配置,后期的版本就不需要了,可以注释掉,我自己用的2022.2.3 -->
        <!--  <dependency>-->
        <!--     <groupId>org.mapstruct</groupId>-->
        <!--     <artifactId>mapstruct-processor</artifactId>-->
        <!--     <version>${org.mapstruct.version}</version>-->
        <!--     <scope>provided</scope>-->
        <!--  </dependency>-->

    </dependencies>

    <!-- 编译插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
            </plugin>
            <!-- 原有工具自带的编译器时无法生成实现类,需要maven 的方式来进行编译,才会生成属性转换的实现类。需添加以下依赖 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                    <!-- lombok的依赖位置一定放在mapstruct之前 否则转换后的对象获取不到属性值 -->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

PS:lombok和mapstruct的版本兼容问题(可以直接使用上面pom里的版本)

1、maven插件要使用3.6.0版本以上;

2、lombok使用1.16.16版本以上;

3、另外编译的lombok和mapstruct的插件不要忘了加上。否则会出现下面的错误:No property named "aaa" exists in source parameter(s). Did you mean "null"?

二、转换实体类

⑴.源实体1:

package com.***;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * 学生
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    /*** ID*/
    private Long userId;

    /*** 姓名*/
    private String name;

    /*** 年龄*/
    private Integer age;

    /*** 手机号*/
    private String phone;

    /*** 性别(1男 2女)*/
    private Integer sex;

    /*** 生日*/
    private String birthday;

    /*** 创建时间*/
    private Date createTime;

}

⑵.源实体2:

package com.***;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 课程
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Course {

    /*** 课程ID*/
    private Long courseId;

    /*** 课程名称*/
    private String courseName;

    /*** 序号*/
    private Integer sortNo;

}

⑶.目标实体1:

package com.***;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * 学生Vo
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentVo {

    /***用户ID */
    private Long userId;

    /*** 姓名*/
    private String name;

    /*** 年龄*/
    private Integer age;

    /*** 手机号*/
    private String phone;

    /*** 性别(1男 2女)*/
    private Integer gender;

    /*** 生日*/
    private String birthday;

    /*** 创建时间*/
    private Date createTime;

}

⑷.目标实体2:

package com.***;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 学生课程Vo
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudentCourseVo {

    /*** 学生姓名*/
    private String name;

    /*** 年龄*/
    private Integer age;

    /*** 性别(1男 2女)*/
    private String gender;

    /*** 生日*/
    private String birthday;

    /*** 课程名称*/
    private String course;

}

⑸.转换Mapper接口:(编译完成后会自动生成相应的实现类)

package com.***;

import com.test.java.domain.entity.Course;
import com.test.java.domain.entity.Student;
import com.test.java.domain.vo.StudentCourseVo;
import com.test.java.domain.vo.StudentVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 转换mapper
 */
@Mapper
public interface StudentWrapper {

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

    // 转换一:实体转实体
    @Mapping(target = "userId", ignore = true)
    @Mapping(source = "sex", target = "gender")
    @Mapping(target = "createTime", expression = "java(new java.util.Date())")
    @Mapping(source = "name", target = "name", defaultValue = "张三")
    StudentVo convertVo(Student student);

    // 转换二:list转list
    List<StudentVo> convertList(List<Student> list);

    // 转换三:多对象转换一个对象
    @Mapping(source = "student.sex", target = "gender")
    @Mapping(source = "course.courseName", target = "course")
    StudentCourseVo convertTo(Student student, Course course);

}

现在就可以直接使用mapper接口进行属性转换了,以下举例有三种转换用法:

1、转换一:对象转对象

2、转换二:集合转集合

3、转换三:多个对象转单一对象

    public static void main(String[] args) {
        // 转换一
        Student student = Student.builder().userId(100L).age(18).phone("13012345678").sex(1).birthday("01-01").build();
        StudentVo convertVo = StudentWrapper.INSTANCE.convertVo(student);
        System.out.println("转换一、实体转实体:" + convertVo);

        // 转换二
        Student student1 = Student.builder().userId(1L).name("张三").age(18).phone("13012345678").sex(1).birthday("01-01").createTime(new Date()).build();
        Student student2 = Student.builder().userId(2L).name("李四").age(20).phone("15026668652").sex(2).birthday("05-01").createTime(new Date()).build();
        List<Student> list = new ArrayList<>();
        list.add(student1);
        list.add(student2);
        List<StudentVo> convertList = StudentWrapper.INSTANCE.convertList(list);
        System.out.println("转换二、list转list:" + convertList);

        // 转换三
        Student student3 = Student.builder().name("小明").age(18).phone("13512365568").sex(1).birthday("05-06").createTime(new Date()).build();
        Course course = Course.builder().courseName("《Java从入门到放弃》").sortNo(1).build();
        StudentCourseVo convertTo = StudentWrapper.INSTANCE.convertTo(student3, course);
        System.out.println("转换三、多对象转换一个对象:" + convertTo);
    }

转换结果:

73f78117b887471b8faca4676b1d9396.jpeg

三、特殊处理

mapper转换接口中,还可以进行字段映射,改变字段类型,指定格式化、赋予默认值、过滤不需要转换的字段、自定义转换等方式,包括一些日期的默认处理,只需在@Mapping注解中添加相应参数即可,如下:

  • 字段映射
    @Mapping(source = "sex", target = "gender")
    StudentVo convertVo(Student student);
source:代表源字段;
target:代表目标字段;
  • 日期格式化
    @Mapping(source = "createTime",target = "createTime",dateFormat = "yyyy-MM-dd HH:mm:ss")
    StudentVo convertVo(Student student);
  • 默认值
    @Mapping(source = "name", target = "name", defaultValue = "张三")
    StudentVo convertVo(Student student);

 defaultValue:转换时,赋予默认值;

  • 过滤不需要转换的字段
    @Mapping(target = "userId", ignore = true)
    StudentVo convertVo(Student student);

ignore:默认为false,不过滤;

  • 自定义转换
    @Mapping(target = "createTime", expression = "java(new java.util.Date())")
    StudentVo convertVo(Student student);

expression:自定义转换规则,当前案例是createTime字段获取当前系统时间

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。

猜你喜欢

转载自blog.csdn.net/weixin_42555014/article/details/129733499