其他分享
首页 > 其他分享> > 初识org.mapstruct:mapstruct

初识org.mapstruct:mapstruct

作者:互联网

文章目录

前言

最近发现了个很好玩的工具包org.mapstruct,里面处理类型转换的功能总觉得很高大上。特此写一篇博客记录测试、使用心得。

简介

平时的开发中,针对与数据库做数据交互操作时,一般定义一个vo或者pojo类,在接收前端页面的参数信息时,会采取定义一个dto类的形式。

但是在开发中,难免会碰见需要将 dto 类转换为对应的 vo 类,达到和数据库数据交互的目的。

以前,我最喜欢采取自己手写get/set的代码,手动将数据信息进行转换。稍微用得高大上点的就是采取BeanUtils.copyProperties(source,target)

但机缘巧合下,发现了更好用的工具类org.mapstruct:mapstruct

他能够让类型转换之间更简单快捷,其次还支持部分参数调用指定Java代码解析!

接下来就一起见证下他的神奇之处。

依赖导入

本次测试环境采取Springboot 2.1.4.RELEASEmapstruct-processor 1.3.0.Final

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
	<dependency>
    	<groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-jdk8</artifactId>
        <version>1.3.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.3.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

常见的几种处理方式

原始方式 get/set

既然文章开头说到pojodto两种类型的转换操作,接下来就新建这两个类。
User.java

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

UserDto.java

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

一般的类型转换,参考下列测试类:

import cn.xj.StartApplication;
import cn.xj.pojo.User;
import cn.xj.pojo.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartApplication.class)
@Slf4j
public class Test1 {
    UserDto userDto = null;
    @Before
    public void before(){
        // 假定这是一个前端传递来得数据信息
        userDto = new UserDto(1L,"xiangjiao dto",22,null);
    }

    @Test
    public void test1(){
        User user = new User();
        user.setId(userDto.getId());
        user.setName(userDto.getName());
        user.setAge(userDto.getAge());
        user.setEmail(userDto.getEmail());

        System.out.println(user);
    }
}

采取一般的get/set方式,手动将指定的类中的数据转换至指定的类中

BeanUtils.copyProperties 实现

BeanUtils.copyProperties也能实现类似上面的功能,如下所示:

UserDto userDto = null;
@Before
public void before(){
    // 假定这是一个前端传递来得数据信息
    userDto = new UserDto(1L,"xiangjiao dto",22,null);
}
@Test
public void test2(){
    User user = new User();
    // 第一个参数表示:源数据对象
    // 第二个参数表示:目标对象
    BeanUtils.copyProperties(userDto,user);

    System.out.println(user);
}

主角 mapstruct 登场

1、简单使用

mapstruct如果需要实现上面的功能,需要编写一个接口,如下所示:

package cn.xj.interfaces;

import cn.xj.pojo.User;
import cn.xj.pojo.UserDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

@Mapper // 和 Mybatis的很像哦
public interface MapStructInf {
    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    MapStructInf instances = Mappers.getMapper(MapStructInf.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象
     * @return 返回的是 目标对象,就是最终的结果对象
     */
    @Mappings({
            @Mapping(source = "id",target = "id"),
            @Mapping(source = "name",target = "name"),
            @Mapping(source = "age",target = "age"),
            @Mapping(source = "email",target = "email")
    })
    User tranceToUser(UserDto userDto);
}

编写一个测试类,进行测试:

import cn.xj.StartApplication;
import cn.xj.interfaces.MapStructInf;
import cn.xj.pojo.User;
import cn.xj.pojo.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartApplication.class)
@Slf4j
public class MapstructTest {

    UserDto userDto = null;
    @Before
    public void before(){
        // 假定这是一个前端传递来得数据信息
        userDto = new UserDto(1L,"xiangjiao dto",22,null);
    }

    @Test
    public void test1(){
        User user = MapStructInf.instances.tranceToUser(userDto);
        System.out.println(user);
    }
}

在这里插入图片描述
初步一看,有些观众大姥爷可能会说:

写这么一大篇的接口,就为了转换一个类,吃多了吧!

上面只是简单的使用测试,接下来看点有意思的。

2、转换集合类

继续在指定的cn.xj.interfaces.MapStructInf接口类中,定义一个转换集合的方法。如下所示:

import cn.xj.pojo.User;
import cn.xj.pojo.UserDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

@Mapper // 和 Mybatis的很像哦
public interface MapStructInf {
    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    MapStructInf instances = Mappers.getMapper(MapStructInf.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象
     * @return 返回的是 目标对象,就是最终的结果对象
     */
//    @Mappings({
//            @Mapping(source = "id",target = "ids"),
//            @Mapping(source = "name",target = "names"),
//            @Mapping(source = "age",target = "ages"),
//            @Mapping(source = "email",target = "emails")
//    })
//    User tranceToUser(UserDto userDto);

    // 转换集合
    List<User> tranceToUserList(List<UserDto> userDtoList);
}

编写测试类进行测试:

import cn.xj.StartApplication;
import cn.xj.interfaces.MapStructInf;
import cn.xj.pojo.User;
import cn.xj.pojo.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartApplication.class)
@Slf4j
public class MapstructTest {

    List<UserDto> userDtoList;
    @Before
    public void before(){
        // 假定这是一个前端传递来得数据信息

        userDtoList = Arrays.asList(
                new UserDto(1L,"xiangjiao dto",11,null),
                new UserDto(2L,"xiangjiao dto",22,null),
                new UserDto(3L,"xiangjiao dto",33,null),
                new UserDto(4L,"xiangjiao dto",44,null));
    }

    @Test
    public void test2(){
        List<User> users = MapStructInf.instances.tranceToUserList(userDtoList);
        users.forEach(e->{
            System.out.println(e);
        });
    }
}

结果却是可行的。
在这里插入图片描述

【注意:】这里有个小细节!

如果两个类的属性名是一样的,可以执行成功!
如果两个类的属性名 不同,则数据转换失败!!

为了测试这个问题,则需要将User.java类中的属性名和UserDto.java类中的属性名区分开。如下所示:

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long ids;
    private String names;
    private Integer ages;
    private String emails;
}

再次指定上面的test2测试方法,控制台打印日志信息如下所示:
在这里插入图片描述
【疑问:】当这种问题出现,如何解决呢?

只需要增加一个配置方法!

import cn.xj.pojo.User;
import cn.xj.pojo.UserDto;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

@Mapper // 和 Mybatis的很像哦
public interface MapStructInf {
    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    MapStructInf instances = Mappers.getMapper(MapStructInf.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象
     * @return 返回的是 目标对象,就是最终的结果对象
     */
    @Mappings({
            @Mapping(source = "id",target = "ids"),
            @Mapping(source = "name",target = "names"),
            @Mapping(source = "age",target = "ages"),
            @Mapping(source = "email",target = "emails")
    })
    User tranceToUser(UserDto userDto);

    // 转换集合
    List<User> tranceToUserList(List<UserDto> userDtoList);
}

再次执行test2(),可以看到控制台打印日志如下所示:
在这里插入图片描述
【原因:】

两个类之间属性别名不一致时,可以采取定义单个类转换的关系,实现集合数据的转换!

3、进阶技能 expression

除了上面的基本操作之外,mapstruct还能支持转换时,采取Java代码方式转换。

比如:

定义一个性别枚举类,但是User.java类接收的是性别名称,UserDto.java类中却传递的是性别编号

首先,定义一个枚举类,提供可以根据编号查询名称的函数

package cn.xj.pojo2;

public enum SexEnum {

    man(1,"男"),
    woman(2,"女");

    private Integer value;
    private String name;
	
	// 注意这里一定要是 static
	// expression 只能调用静态方法
    public static String getValByName(Integer value){
        String names = null;
        for (SexEnum sexEnum : values()){
            Integer value1 = sexEnum.getValue();
            if(value1.equals(value)){
                names = sexEnum.getName();
                break;
            }
        }
        return names;
    }

    SexEnum(Integer value, String name) {
        this.value = value;
        this.name = name;
    }

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

    public String getName() {
        return name;
    }

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

再修改对应的User.java类:

保证User.java类接收性别别名

package cn.xj.pojo2;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long ids;
    private String names;
    private Integer ages;
    private String emails;

    private String sex; // 性别别名
}

定义前端页面数据接收类UserDto.java

保证性别信息采取编号接收

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
    private Long id;
    private String name;
    private Integer age;
    private String email;

    private Integer sexNum; // 性别编号
}

定义转换方式:

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

@Mapper // 和 Mybatis的很像哦
public interface MapStructInf2 {
    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    MapStructInf2 instances = Mappers.getMapper(MapStructInf2.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param userDto 这个参数就是 源对象,也就是需要 被复制 的对象
     * @return 返回的是 目标对象,就是最终的结果对象
     */
    @Mappings({
            @Mapping(source = "id",target = "ids"),
            @Mapping(source = "name",target = "names"),
            @Mapping(source = "age",target = "ages"),
            @Mapping(source = "email",target = "emails"),
            @Mapping(target = "sex",expression = "java(cn.xj.pojo2.SexEnum.getValByName(userDto.getSexNum()))")
    })
    User tranceToUser(UserDto userDto);
}

执行后的结果如下所示:
在这里插入图片描述

参考资料

org.mapstruct:mapstruct 包的使用

mapstruct 实体转换及List转换

代码下载

gitee 仓库地址

标签:mapstruct,Mapping,初识,import,org,UserDto,public
来源: https://blog.csdn.net/qq_38322527/article/details/123647995