美文网首页
映射框架MapStruct

映射框架MapStruct

作者: AC编程 | 来源:发表于2021-11-23 10:58 被阅读0次

一、MapStruct

开发中,我们经常需要将PO转DTO、DTO转PO等一些实体间的转换。比较出名的有BeanUtil 和ModelMapper等,它们使用简单,但是在稍显复杂的业务场景下力不从心。MapStruct这个插件可以用来处理domin实体类与model类的属性映射,可配置性强。只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。MapStruct官网地址: http://mapstruct.org/

二、MapStruct优势

为什么推荐这个框架呢,下面说说原因和他的优势

  • 原因一: 很多项目大量映射通过手动get、set,这种写法非常繁琐无味,而且没有技术含量。甚至中间还牵涉了很多类型转换,嵌套之类的繁琐操作,非常的愚蠢。

  • 原因二: 有人说apache的BeanUtil. copyProperties可以实现,但是性能差而且容易出异常,很多规范严禁使用这种途径。以下是对几种对象映射框架的对比,大多数情况下MapStruct性能最高。原理类似于lombok,MapStruct都是在编译期进行实现,而且基于Getter、Setter,,没有 使用反射所以-般不存在运行时性能问题。

  • 原因三: 方便的映射操作,对我们平台统一代码有极其重大的意义。简化一套代码真正兼容多个平台的代码实现。使得开发人员多年对于多个联网平台统一代码的幻想又进了一步。

  • 原因四: 在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。MapStruct 就是这样的一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。

  • 原因五: 对于包装类是自动拆箱封箱操作的,并且是线程安全的。MapStruct不单单有这些功能,还有其他一些复杂的功能:设置转换默认值和常量。当目标值是null时我们可以设置其默认值。

三、MapStruct使用

3.1 单个对象转换
3.1.1 新建maven项目,引入依赖MapStruct、Lombok

pom.xml

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

    <properties>
        <!-- lombok要与mapstruct版本匹配,用同一时间的版本,不然会出现各种问题 -->
        <org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.6</org.projectlombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <!-- 要与mapstruct版本匹配,用同一时间的版本,不然会出现各种问题 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
3.1.2 编写代码

entity

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Integer id;

    private String name;

    private Integer age;

    private String sex;
}

vo

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserVO {

    private Integer id;

    /**
     * User的属性是name
     */
    private String userName;

    private Integer age;

    private String sex;
}

convert

import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
 *          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
 */
@Mapper
public interface UserConvert {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    UserConvert instance = Mappers.getMapper(UserConvert.class);

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

    @Mappings({@Mapping(source = "userName",target = "name")})
    User toEntity(UserVO userVO);
}

@Mapper 只有在接口加上这个注解, MapStruct 才会去实现该接口

@Mapper 里有个 componentModel 属性,主要是指定实现类的类型,一般用到两个:

  • default: 默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象

  • spring: 在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入

@Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性

测试Client

public class Client {

    public static void main(String[] args) {
        poToVO();

        System.out.println();

        voTOPo();
    }

    private static void poToVO(){
        User user = User.builder()
                .id(1)
                .name("AlanChen")
                .age(18)
                .sex("1")
                .build();
        System.out.println("user:"+user);

        UserVO userVO = UserConvert.instance.toVO(user);
        System.out.println("userVO:"+userVO);
    }

    private static void voTOPo(){
        UserVO userVO = UserVO.builder()
                .id(1)
                .userName("AlanChen")
                .age(18)
                .sex("1")
                .build();
        System.out.println("userVO:"+userVO);

        User user = UserConvert.instance.toEntity(userVO);
        System.out.println("user:"+user);
    }
}

运行结果

user:User(id=1, name=AlanChen, age=18, sex=1)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1)

userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1)
user:User(id=1, name=AlanChen, age=18, sex=1)

启动运行,会在target目录下自动生成转换实现类

实现类
3.1.3 跳坑

一开始没有注意MapStruct与Lombok匹配的问题,导出出现了一些问题,遇到的问题有以下两个

问题一:MapStruct生成的实现类缺失Entity转VO的具体的实现

没有设置属性

问题二:出现java: No property named “XXX“ exists in source parameter(s). Did you mean “null“

MapStruct与Lombok的版本怎么匹配,我也不太清楚,我尝试着采用MapStruct、Lombok同一时期的版本,如mapstruct V1.3.0.Finalprojectlombok V1.18.6都是2019年二月份的版本,问题得到解决。

  <!-- lombok要与mapstruct版本匹配,用同一时间的版本,不然会出现各种问题 -->
        <org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
        <org.projectlombok.version>1.18.6</org.projectlombok.version>

问题三:如果对象有继承其他父类,转换也会失败

3.2 对象转换返回List

转化 List<> 集合时必须有 实体转化,因为在实现中,List 转换是 for循环调用 实体转化的。所以当属性名不对应时,应该在 实体转化进行 @Mappings 的属性名映射配置,然后list的转换也会继承这和属性的映射。

@Mapper
public interface UserConvert {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    UserConvert instance = Mappers.getMapper(UserConvert.class);

    /**
     * 转换成List
     * @param users
     * @return
     */
    List<UserVO> toVOList(List<User> users);
}

完整代码为

import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

/**
 * @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
 *          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
 */
@Mapper
public interface UserConvert {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    UserConvert instance = Mappers.getMapper(UserConvert.class);

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

    @Mappings({@Mapping(source = "userName",target = "name")})
    User toEntity(UserVO userVO);

    /**
     * 转换成List
     * @param users
     * @return
     */
    List<UserVO> toVOList(List<User> users);
}

测试Client

public class Client {

    public static void main(String[] args) {
        toList();
    }
    private static void toList(){
        List<User> userList = new ArrayList<User>();

        User user1 = User.builder()
                .id(1)
                .name("AlanChen")
                .age(18)
                .sex("1")
                .build();
        userList.add(user1);

        User user2 = User.builder()
                .id(2)
                .name("AlanChen2")
                .age(20)
                .sex("0")
                .build();
        userList.add(user2);

        List<UserVO> userVOList = UserConvert.instance.toVOList(userList);
        System.out.println("userVOList:"+userVOList);
    }
}

运行结果

userVOList:[UserVO(id=1, userName=AlanChen, age=18, sex=1), UserVO(id=2, userName=AlanChen2, age=20, sex=0)]

3.3 属性类型不同,自定义转换类

现在在User里加一个是否停用的属性private boolean stop;boolean类型 ,但UserVO里是否停用的属性private String stop;为String类型,二者属性不同,我们需要自己写一个转换类

public class UserTransform {

    public String booleanToString(boolean value){
        if(value){
            return "停用";
        }
        return "未停用";
    }
    public boolean strToBoolean(String str){
        if ("停用".equals(str)) {
            return true;
        }
        return false;
    }
}

UserConvert加注解参数

@Mapper(uses = UserTransform.class)
import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

/**
 * @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
 *          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
 */
@Mapper(uses = UserTransform.class)
public interface UserConvert {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    UserConvert instance = Mappers.getMapper(UserConvert.class);

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

    @Mappings({@Mapping(source = "userName",target = "name")})
    User toEntity(UserVO userVO);

    /**
     * 转换成List
     * @param users
     * @return
     */
    List<UserVO> toVOList(List<User> users);
}

Client测试代码

public class Client {

    public static void main(String[] args) {
        poToVO();

        System.out.println();

        voTOPo();
    }

    private static void poToVO(){
        User user = User.builder()
                .id(1)
                .name("AlanChen")
                .age(18)
                .sex("1")
                .stop(false)
                .build();
        System.out.println("user:"+user);

        UserVO userVO = UserConvert.instance.toVO(user);
        System.out.println("userVO:"+userVO);
    }

    private static void voTOPo(){
        UserVO userVO = UserVO.builder()
                .id(1)
                .userName("AlanChen")
                .age(18)
                .sex("1")
                .stop("停用")
                .build();
        System.out.println("userVO:"+userVO);

        User user = UserConvert.instance.toEntity(userVO);
        System.out.println("user:"+user);
    }
}

运行结果

user:User(id=1, name=AlanChen, age=18, sex=1, stop=false)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=未停用)

userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=停用)
user:User(id=1, name=AlanChen, age=18, sex=1, stop=true)

3.4 dateFormat配置日期格式

在User类里继续加一个生日字段private Date birthday;,UserVO类里加生日字段private String birthday;

在UserConvert里指定dateFormat

import com.alanchen.mapstruct.entity.User;
import com.alanchen.mapstruct.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

/**
 * @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
 *          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
 */
@Mapper(uses = UserTransform.class)
public interface UserConvert {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    UserConvert instance = Mappers.getMapper(UserConvert.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param user 这个参数就是源对象,也就是需要被复制的对象
     * @return 返回的是目标对象,就是最终的结果对象
     */
    @Mappings({@Mapping(source = "name",target = "userName"),
            @Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")})
    UserVO toVO(User user);

    @Mappings({@Mapping(source = "userName",target = "name"),
            @Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")})
    User toEntity(UserVO userVO);

    /**
     * 转换成List
     * @param users
     * @return
     */
    List<UserVO> toVOList(List<User> users);
}

Client测试类

public class Client {

    public static void main(String[] args) {
        poToVO();

        System.out.println();

        voTOPo();
    }

    private static void poToVO(){
        User user = User.builder()
                .id(1)
                .name("AlanChen")
                .age(18)
                .sex("1")
                .stop(false)
                .birthday(new Date())
                .build();
        System.out.println("user:"+user);

        UserVO userVO = UserConvert.instance.toVO(user);
        System.out.println("userVO:"+userVO);
    }

    private static void voTOPo(){
        UserVO userVO = UserVO.builder()
                .id(1)
                .userName("AlanChen")
                .age(18)
                .sex("1")
                .stop("停用")
                .birthday("1990-05-20")
                .build();
        System.out.println("userVO:"+userVO);

        User user = UserConvert.instance.toEntity(userVO);
        System.out.println("user:"+user);
    }
}

运行结果

user:User(id=1, name=AlanChen, age=18, sex=1, stop=false, birthday=Mon Nov 22 19:02:44 CST 2021)
userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=未停用, birthday=2021-11-22)

userVO:UserVO(id=1, userName=AlanChen, age=18, sex=1, stop=停用, birthday=1990-05-20)
user:User(id=1, name=AlanChen, age=18, sex=1, stop=true, birthday=Sun May 20 00:00:00 CDT 1990)
3.5 ignore

ignore: 忽略这个字段

3.6 多对一映射

MapStruct 可以将几种类型的对象映射为另外一种类型,比如将多个entity对象转换为VO。例如:两个entity对象 Item 和 Sku,一个VO对象SkuVO

entity

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Item {

    private Long id;

    private String title;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Sku {

    private String code;

    private Integer price;
}

convert

@Mapper
public interface ItemConvert {

    ItemConvert instance = Mappers.getMapper(ItemConvert.class);

    @Mappings({
            @Mapping(source = "sku.id",target = "skuId"),
            @Mapping(source = "sku.code",target = "skuCode"),
            @Mapping(source = "sku.price",target = "skuPrice"),
            @Mapping(source = "item.id",target = "itemId"),
            @Mapping(source = "item.title",target = "itemName")

    })

    SkuVO toVO(Item item, Sku sku);
}

四、源码

alanchenyan/mapstruct

相关文章

网友评论

      本文标题:映射框架MapStruct

      本文链接:https://www.haomeiwen.com/subject/qpvbtrtx.html