【MapStruct】还在用BeanUtils?不如试试MapStruct

1. 什么是MapStruct

MapStruct是一个Java注解处理器,它可以简化Java bean之间的转换。它使用基于生成器的方法创建类型安全的映射代码,这些代码在编译时生成,并且比反射更快、更可靠。使用MapStruct可以避免手动编写大量重复的转换代码,从而提高生产力和代码质量。

MapStruct通过使用注解,在源代码中指定映射规则,MapStruct可以自动生成转换器代码。MapStruct支持各种转换场景,包括简单类型、集合、继承、日期、枚举、嵌套映射等等。同时,它还能够与Spring和CDI等IoC容器无缝集成,方便地将MapStruct转换器注入到应用程序中。

MapStruct的官网:MapStruct – Java bean mappings, the easy way!

在开发中比较常用的用来实现JavaBean之间的转换应该就是org.springframework.beans.BeanUtils,它俩有以下区别:

  1. 编译时生成代码 vs 运行时反射MapStruct生成的映射代码是在编译时生成的,而BeanUtils则是在运行时使用反射机制实现转换
  2. 性能和可扩展性:由于MapStruct生成的代码是类型安全的,因此可以比使用反射更加高效和可靠。同时,MapStruct还能够自定义转换逻辑并支持扩展,使得它更加灵活和可扩展。
  3. 集成方式:MapStruct可以无缝集成到Spring中,也可以与其他IoC容器结合使用;而BeanUtils是Spring框架自带的工具类。
  4. 映射规则的定义方式:MapStruct使用基于注解的方式在源代码中定义映射规则,而BeanUtils则需要手动编写复杂的转换方法。

2. 为什么使用MapStruct

在一些高并发的场景,性能是开发者十分重视的,BeanUtils虽然也可以方便地完成JavaBean之间的转换,但是由于其底层是基于反射实现的,在高并发场景下难免会出现大规模的数据处理和转换操作,这时候还是用BeanUtils会导致接口响应速度有所下降。

这时候,最最最高效的方法就是手动get/set,但是这种需要反复写大量重复的转换代码,并且这些代码难以被反复利用,于是就考虑使用MapStruct。

MapStruct是一种基于注解的代码生成器,它通过生成优化的映射代码来实现高性能的Bean映射。与BeanUtils相比,MapStruct在生成的映射代码中使用了更少的反射调用,并且在类型转换时可以直接使用Javac编译器已经提供的类型转换逻辑,从而避免了额外的性能开销。此外,由于MapStruct是基于注解的,它还可以提供更好的类型检查和编译时错误提示。

以下是当前比较常用的JavaBean之间转化的工具的性能对比:

image-20230602212240984

可见,随着转化次数的增加只有MapStruct的性能最接近get/set的效率。

因此,在高并发场景下,使用MapStruct可以更有效地利用系统资源,提高系统的吞吐量和响应速度。


3. 如何使用MapStruct

接下来用一个案例来说说如何使用MapStruct,更多详细的用法可以查看官方文档。

温馨提示:在生成get/set方法的时候,最好不要用lombok,不然有可能会出现一些奇奇怪怪的问题

先引入依赖:

<!-- MapStruct begin -->
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
</dependency>
<!-- MapStruct end -->

Student类:

public class Student {
    private String sId;
    private String sName;
    private String sSex;

    public String getsId() {
        return sId;
    }

    public void setsId(String sId) {
        this.sId = sId;
    }

    public String getsName() {
        return sName;
    }

    public void setsName(String sName) {
        this.sName = sName;
    }

    public String getsSex() {
        return sSex;
    }

    public void setsSex(String sSex) {
        this.sSex = sSex;
    }
}

StudentVo类:

public class StudentVo {
    private String id;
    private String name;
    private String sSex;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getsSex() {
        return sSex;
    }

    public void setsSex(String sSex) {
        this.sSex = sSex;
    }
}

现在模拟两个业务:

  1. Student类 转 StudentVo类
  2. StudentVo类 转 Student类

首先,需要定义一个对象接口映射的接口

/**
 * @description:对象接口映射
 * @author:lrk
 * @date: 2023/6/2
 */
@MapperConfig
public interface IMapping<SOURCE, TARGET> {
    /**
     * 映射同名属性
     * @param var1 源
     * @return     结果
     */
    @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    TARGET sourceToTarget(SOURCE var1);

    /**
     * 映射同名属性,反向
     * @param var1 源
     * @return     结果
     */
    @InheritInverseConfiguration(name = "sourceToTarget")
    SOURCE targetToSource(TARGET var1);

    /**
     * 映射同名属性,集合形式
     * @param var1 源
     * @return     结果
     */
    @InheritConfiguration(name = "sourceToTarget")
    List<TARGET> sourceToTarget(List<SOURCE> var1);

    /**
     * 反向,映射同名属性,集合形式
     * @param var1 源
     * @return     结果
     */
    @InheritConfiguration(name = "targetToSource")
    List<SOURCE> targetToSource(List<TARGET> var1);

    /**
     * 映射同名属性,集合流形式
     * @param stream 源
     * @return       结果
     */
    List<TARGET> sourceToTarget(Stream<SOURCE> stream);

    /**
     * 反向,映射同名属性,集合流形式
     * @param stream 源
     * @return       结果
     */
    List<SOURCE> targetToSource(Stream<TARGET> stream);
}

这个接口上需要加org.mapstruct.MapperConfig的注解,表示这是一个构造器。

接下来,就需要构造一个具体类的转换配置了,需要继承IMapping接口

这里就模拟转换两个类的相互转化,其他比如List的转换可以自主实现

/**
 * @description:学生类转换配置
 * @author:lrk
 * @date: 2023/6/2
 */
@Mapper(
        // 指定依赖注入框架
        componentModel = "spring",
        unmappedTargetPolicy = ReportingPolicy.IGNORE,
        unmappedSourcePolicy = ReportingPolicy.IGNORE
)
public interface StudentMapping extends IMapping<Student, StudentVo>{

    /**
     * Student 转 StudentVo
     * @param var1 源
     * @return
     */
    @Mapping(target = "id", source = "sId")
    @Mapping(target = "name", source = "sName")
    @Override
    StudentVo sourceToTarget(Student var1);

    /**
     * StudentVo 转 Student
     * @param var1 源
     * @return
     */
    @Mapping(target = "sId", source = "id")
    @Mapping(target = "sName", source = "name")
    @Override
    Student targetToSource(StudentVo var1);
}
  1. 继承接口的泛型为<Student, StudentVo>,指定了Student是源,StudentVo是目标。
  2. 接口上需要有org.mapstruct.Mapper注解,注解里面有三个参数
    • componentModel:指定依赖注入框架,目前只支持springCDI
    • unmappedTargetPolicy:在映射方法的目标对象的属性未填充源值时应用的默认报告策略。
      • ERROR:任何未映射的目标属性都将导致映射代码生成失败
      • WARN:任何未映射的目标属性都将在构建时导致警告
      • IGNORE:忽略未映射的目标属性
    • unmappedSourcePolicy:同上
  3. 认真观察Student与StudentVo可以看见,两个类除了成员变量sSex一样外,其余的成员变量属性均不一样,这时候可以使用org.mapstruct.Mapping注解在接口方法上
    • Mapping:当属性在目标实体中具有不同的名称时,可以通过注释指定其名称

编写测试代码

@Resource
private IMapping<Student, StudentVo> studentMapping;


@Test
public void test_mapstruct() {
    Student student = new Student();
    student.setsId("10086");
    student.setsName("张三");
    student.setsSex("男");
    StudentVo studentVo = studentMapping.sourceToTarget(student);
    log.info("Student:{} 转 StudentVo:{}", JSONUtil.toJsonStr(student), JSONUtil.toJsonStr(studentVo));
    Student student1 = studentMapping.targetToSource(studentVo);
    log.info("StudentVo:{} 转 Student:{}", JSONUtil.toJsonStr(studentVo), JSONUtil.toJsonStr(student1));

}

image-20230602214035903

细心的你估计已经发现了,为什么这里可以用@ResourceIMapping<Student, StudentVo>注入,代码中并没有看见将其放进Spring容器呀?

别急,前面说到MapStruct的转换代码是在编译时生成的,查看编译生成的代码可以发现其中已经加入了@Component注解并实现了StudentMapping

@Component
public class StudentMappingImpl implements StudentMapping {
    public StudentMappingImpl() {
    }

    public List<StudentVo> sourceToTarget(List<Student> var1) {
        if (var1 == null) {
            return null;
        } else {
            List<StudentVo> list = new ArrayList(var1.size());
            Iterator var3 = var1.iterator();

            while(var3.hasNext()) {
                Student student = (Student)var3.next();
                list.add(this.sourceToTarget(student));
            }

            return list;
        }
    }

    public List<Student> targetToSource(List<StudentVo> var1) {
        if (var1 == null) {
            return null;
        } else {
            List<Student> list = new ArrayList(var1.size());
            Iterator var3 = var1.iterator();

            while(var3.hasNext()) {
                StudentVo studentVo = (StudentVo)var3.next();
                list.add(this.targetToSource(studentVo));
            }

            return list;
        }
    }

    public List<StudentVo> sourceToTarget(Stream<Student> stream) {
        return stream == null ? null : (List)stream.map((student) -> {
            return this.sourceToTarget(student);
        }).collect(Collectors.toCollection(ArrayList::new));
    }

    public List<Student> targetToSource(Stream<StudentVo> stream) {
        return stream == null ? null : (List)stream.map((studentVo) -> {
            return this.targetToSource(studentVo);
        }).collect(Collectors.toCollection(ArrayList::new));
    }

    public StudentVo sourceToTarget(Student var1) {
        if (var1 == null) {
            return null;
        } else {
            StudentVo studentVo = new StudentVo();
            studentVo.setId(var1.getsId());
            studentVo.setName(var1.getsName());
            studentVo.setsSex(var1.getsSex());
            return studentVo;
        }
    }

    public Student targetToSource(StudentVo var1) {
        if (var1 == null) {
            return null;
        } else {
            Student student = new Student();
            student.setsId(var1.getId());
            student.setsName(var1.getName());
            student.setsSex(var1.getsSex());
            return student;
        }
    }
}

这是因为前面在StudentMapping指定了依赖注入框架为Spring的原因,所以生成的代码自动将其放进容器,方便我们开发者使用。

 

摘要由CSDN通过智能技术生成

1、什么是MapStruct

1.1 JavaBean 的困扰

对于代码中 JavaBean之间的转换, 一直是困扰我很久的事情。在开发的时候我看到业务代码之间有很多的 JavaBean 之间的相互转化, 非常的影响观感,却又不得不存在。我后来想的一个办法就是通过反射,或者自己写很多的转换器。

第一种通过反射的方法确实比较方便,但是现在无论是 BeanUtils, BeanCopier 等在使用反射的时候都会影响到性能。虽然我们可以进行反射信息的缓存来提高性能。但是像这种的话,需要类型和名称都一样才会进行映射,有很多时候,由于不同的团队之间使用的名词不一样,还是需要很多的手动 set/get 等功能。

第二种的话就是会很浪费时间,而且在添加新的字段的时候也要进行方法的修改。不过,由于不需要进行反射,其性能是很高的。

1.2 MapStruct 带来的改变

MapSturct 是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。

  • 注解处理器

  • 可以生成 JavaBean 之间那的映射代码

  • 类型安全,高性能,无依赖性

2、MapStruct 入门

2.1 添加依赖

 
  1. <dependency>
  2.     <groupId>org.projectlombok</groupId>
  3.     <artifactId>lombok</artifactId>
  4.     <version>1.16.20</version>
  5.     <scope>provided</scope>
  6. </dependency>
  7. <dependency>
  8.     <groupId>org.mapstruct</groupId>
  9.     <artifactId>mapstruct-jdk8</artifactId>
  10.     <version>${org.mapstruct.version}</version>
  11. </dependency>
  12. <dependency>
  13.     <groupId>org.mapstruct</groupId>
  14.     <artifactId>mapstruct-processor</artifactId>
  15.     <version>${org.mapstruct.version}</version>
  16. </dependency>
  17. <dependency>
  18.     <groupId>cn.hutool</groupId>
  19.     <artifactId>hutool-all</artifactId>
  20.     <version>5.1.0</version>
  21. </dependency>
  22. <dependency>
  23.     <groupId>junit</groupId>
  24.     <artifactId>junit</artifactId>
  25.     <version>4.12</version>
  26.     <scope>test</scope>
  27. </dependency>
 

2.2 po类

 
  1. @Data
  2. public class User {
  3.     private Integer id;
  4.     private String name;
  5.     private String address;
  6.     private Date birth;
  7. }
 

2.3 dto类

 
  1. @Data
  2. public class UserDto implements Serializable {
  3.     private Integer id;
  4.     private String name;
  5.     private String address;
  6.     private Date birth;
  7. }
 

2.4 创建转换接口

 
  1. //可以使用abstract class代替接口
  2. @Mapper
  3. public interface UserMapper {
  4.     
  5.     UserDto userToUserDto(User user);
  6.     //集合
  7.     List<UserDto> userToUserDto(List<User> users);
  8. }
 

2.5 测试方法

 
  1. @Test
  2. public void userPoToUserDto() {
  3.     User user =new User();
  4.     user.setId(1);
  5.     user.setName("myx");
  6.     user.setAddress("江苏苏州");
  7.     user.setBirth(new Date());
  8.     UserMapper mapper = Mappers.getMapper(UserMapper.class);
  9.     UserDto userDto = mapper.userToUserDto(user);
  10.     System.out.println(userDto);
  11. }
 

2.6 运行效果

2.7 查看编译的class

底层通过自动取值赋值操作完成

3、MapStruct优点分析

3.1 性能高

这是相对反射来说的,反射需要去读取字节码的内容,花销会比较大。而通过 MapStruct 来生成的代码,其类似于人手写。速度上可以得到保证。

3.2 使用简单

如果是完全映射的,使用起来肯定没有反射简单。用类似 BeanUtils 这些工具一条语句就搞定了。但是,如果需要进行特殊的匹配(特殊类型转换,多对一转换等),其相对来说也是比较简单的。

基本上,使用的时候,我们只需要声明一个接口,接口下写对应的方法,就可以使用了。当然,如果有特殊情况,是需要额外处理的。

3.3 代码独立

生成的代码是对立的,没有运行时的依赖。

3.4 易于 debug

在我们生成的代码中,我们可以轻易的进行 debug。

4、MapStruct使用案例

4.1 属性名称相同

在实现类的时候,如果属性名称相同,则会进行对应的转化。通过此种方式,我们可以快速的编写出转换的方法。(入门案例)

4.2 属性名不相同

属性名不相同,在需要进行互相转化的时候,则我们可以通过@Mapping 注解来进行转化。

 
  1. @Data
  2. public class UserDto implements Serializable {
  3.     private Integer id;
  4.     private String name;
  5.     private String address;
  6.     private Date birth;
  7.     private String password;
  8. }
  9. @Data
  10. public class User {
  11.     private Integer id;
  12.     private String name;
  13.     private String address;
  14.     private Date birth;
  15.     private String pwd;
  16. }
  17. @Mapper
  18. public interface UserMapper {
  19.     //单个属性
  20.     //@Mapping(source = "pwd",target = "password")
  21.     //多个属性
  22.     @Mappings({
  23.             @Mapping(source = "pwd",target = "password")
  24.     })
  25.     UserDto userToUserDto(User user);
  26. }
 
  • source 需要转换的对接,通常是入参

  • target 转换的对接,通常是出参

  • ignore 忽略,默认false不忽略,需要忽略设置为true

  • defaultValue 默认值

  • expressions 可以通过表达式来构造一些简单的转化关系。虽然设计的时候想兼容很多语言,不过目前只能写Java代码。

 
  1. @Mappings({
  2.             @Mapping(source = "birthdate", target = "birth"),//属性名不一致映射
  3.             @Mapping(target = "birthformat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthdate(),\"yyyy-MM-dd HH:mm:ss\"))"),//自定义属性通过java代码映射
  4.     })
  5. public PersonVo PersonToPersonVo(Person person);
 

这里用到演示了如何使用TimeAndFormat对time和format操作,这里必须要指定需要使用的Java类的完整包名,不然编译的时候不知道你使用哪个Java类,会报错。

 
  1. @Test
  2. public void userPoToUserDto() {
  3.     User user =new User();
  4.     user.setId(1);
  5.     user.setName("myx");
  6.     user.setAddress("江苏苏州");
  7.     user.setBirth(new Date());
  8.     user.setPwd("123456");
  9.     UserMapper mapper = Mappers.getMapper(UserMapper.class);
  10.     UserDto userDto = mapper.userToUserDto(user);
  11.     System.out.println(userDto);
  12. }
 

4.3 转换非基础类型属性

如果subUser与subUserDto字段名称相同直接配置即可完成(对象类型,包括list)

 
  1. @Data
  2. public class UserDto implements Serializable {
  3.     private Integer id;
  4.     private String name;
  5.     private String address;
  6.     private Date birth;
  7.     private String password;
  8.     private List<SubUserDto> subUserDto;
  9. }
  10. @Data
  11. public class User {
  12.     private Integer id;
  13.     private String name;
  14.     private String address;
  15.     private Date birth;
  16.     private String pwd;
  17.     private List<SubUser> subUser;
  18. }
  19. @Mappings({
  20.         @Mapping(source = "pwd",target = "password"),
  21.         @Mapping(source = "subUser", target = "subUserDto")
  22. })
  23. UserDto userToUserDto(User user);
 

4.4 Mapper 中使用自定义的转换

有时候,对于某些类型,无法通过代码生成器的形式来进行处理。那么, 就需要自定义的方法来进行转换。这时候,我们可以在接口(同一个接口,后续还有调用别的 Mapper 的方法)中定义默认方法(Java8及之后)。

 
  1. @Data
  2. public class UserDto implements Serializable {
  3.     private Integer id;
  4.     private String name;
  5.     private String address;
  6.     private Date birth;
  7.     private String password;
  8.     private SubUserDto subUserDto;
  9. }
  10.  
  11. @Data
  12. public class SubUserDto {
  13.     private Boolean result;
  14.     private String name;
  15. }
  16. @Data
  17. public class User {
  18.     private Integer id;
  19.     private String name;
  20.     private String address;
  21.     private Date birth;
  22.     private String pwd;
  23.     private SubUser subUser;
  24. }
  25.  
  26. @Data
  27. public class SubUser {
  28.     private Integer deleted;
  29.     private String name;
  30. }
  31. @Mapper
  32. public interface UserMapper {
  33.     @Mappings({
  34.             @Mapping(source = "pwd",target = "password"),
  35.             @Mapping(source = "subUser", target = "subUserDto")
  36.     })
  37.     UserDto userToUserDto(User user);
  38.  
  39.     default SubUserDto subSource2subTarget(SubUser subUser) {
  40.         if (subUser == null) {
  41.             return null;
  42.         }
  43.         SubUserDto subUserDto = new SubUserDto();
  44.         subUserDto.setResult(!subUser.getDeleted().equals(0));
  45.         subUserDto.setName(subUser.getName()==null?"":subUser.getName());
  46.         return subUserDto;
  47.     }
  48. }
 

只能存在一个default修饰的方法

 
  1. @Test
  2. public void userPoToUserDto() {
  3.     User user =new User();
  4.     user.setId(1);
  5.     user.setName("myx");
  6.     user.setAddress("江苏苏州");
  7.     user.setBirth(new Date());
  8.     user.setPwd("123456");
  9.     SubUser subUser =new SubUser();
  10.     subUser.setDeleted(0);
  11.     subUser.setName("rkw");
  12.     user.setSubUser(subUser);
  13.     UserMapper mapper = Mappers.getMapper(UserMapper.class);
  14.     UserDto userDto = mapper.userToUserDto(user);
  15.     System.out.println(userDto);
  16. }
 

4.5 多转一

我们在实际的业务中少不了将多个对象转换成一个的场景。MapStruct 当然也支持多转一的操作。

 
  1. @Data
  2. public class SubUser {
  3.     private Integer deleted;
  4.     private String name;
  5. }
  6. @Data
  7. public class User {
  8.     private Integer id;
  9.     private String name;
  10.     private String address;
  11.     private Date birth;
  12.     private String pwd;
  13. }
  14. @Mapper
  15. public interface UserMapper {
  16.     @Mappings({
  17.             @Mapping(source = "user.pwd",target = "password"),
  18.             @Mapping(source = "subUser.name", target = "name")
  19.     })
  20.     NewUserDto userToUserDto(User user,SubUser subUser);
  21. }
  22. @Test
  23. public void userPoToUserDto() {
  24.     User user =new User();
  25.     user.setId(1);
  26.     user.setName("myx");
  27.     user.setAddress("江苏苏州");
  28.     user.setBirth(new Date());
  29.     user.setPwd("123456");
  30.     SubUser subUser =new SubUser();
  31.     subUser.setDeleted(0);
  32.     subUser.setName("rkw");
  33.     UserMapper mapper = Mappers.getMapper(UserMapper.class);
  34.     NewUserDto userDto = mapper.userToUserDto(user,subUser);
  35.     System.out.println(userDto);
  36. }
 

4.5.1 遵循原则

  • 当多个对象中, 有其中一个为 null, 则会直接返回 null

  • 如一对一转换一样, 属性通过名字来自动匹配。因此, 名称和类型相同的不需要进行特殊处理

  • 当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义(不指定会报错)。如上面的 name

属性也可以直接从传入的参数来赋值

 
  1. @Mapping(source = "person.description", target = "description")
  2. @Mapping(source = "name", target = "name")
  3. DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, String name);
 

4.6 更新 Bean 对象

有时候,我们不是想返回一个新的 Bean 对象,而是希望更新传入对象的一些属性。这个在实际的时候也会经常使用到。

 
  1. @Mapper
  2. public interface UserMapper {
  3.  
  4.     NewUserDto userToNewUserDto(User user);
  5.  
  6.     /**
  7.      * 更新, 注意注解 @MappingTarget
  8.      * 注解 @MappingTarget后面跟的对象会被更新。
  9.      */
  10.     void updateDeliveryAddressFromAddress(SubUser subUser,@MappingTarget NewUserDto newUserDto);
  11. }
  12. @Test
  13. public void userPoToUserDto() {
  14.     User user =new User();
  15.     user.setId(1);
  16.     user.setName("myx");
  17.     user.setAddress("江苏苏州");
  18.     user.setBirth(new Date());
  19.     SubUser subUser =new SubUser();
  20.     subUser.setDeleted(0);
  21.     subUser.setName("rkw");
  22.     UserMapper mapper = Mappers.getMapper(UserMapper.class);
  23.     NewUserDto userDto = mapper.userToNewUserDto(user);
  24.     mapper.updateDeliveryAddressFromAddress(subUser,userDto);
  25.     System.out.println(userDto);
  26. }
 

4.7 map映射

 
  1. @MapMapping(valueDateFormat ="yyyy-MM-dd HH:mm:ss")
  2. public Map<String ,String> DateMapToStringMap(Map<String,Date> sourceMap);
  3. @Test
  4. public void mapMappingTest(){
  5.     Map<String,Date> map=new HashMap<>();
  6.     map.put("key1",new Date());
  7.     map.put("key2",new Date(new Date().getTime()+9800000));
  8.     Map<String, String> stringObjectMap = TestMapper.MAPPER.DateMapToStringMap(map);
  9. }
 

4.8 多级嵌套

只需要在mapper接口中定义相关的类型转换方法即可,list类型也适用

4.8.1 方式1

 
  1. @Data
  2. public class User {
  3.     private Integer id;
  4.     private String name;
  5.     private String address;
  6.     private Date birth;
  7.     private Boolean isDisable;
  8.     private List<SubUser> user;
  9. }
  10.  
  11. @Data
  12. public class SubUser {
  13.     private Integer deleted;
  14.     private String name;
  15.     private List<SubSubUser> subUser;
  16. }
  17. @Data
  18. public class SubSubUser {
  19.     private String aaa;
  20.     private String ccc;
  21. }
  22. @Data
  23. public class UserDto implements Serializable {
  24.     private Integer id;
  25.     private String name;
  26.     private String address;
  27.     private Date birth;
  28.     private String isDisable;
  29.     private List<SubUserDto> user;
  30. }
  31. @Data
  32. public class SubUserDto {
  33.     private Integer deleted;
  34.     private String name;
  35.     private List<SubSubUserDto> subUser;
  36. }
  37. @Data
  38. public class SubSubUserDto {
  39.     private String aaa;
  40.     private  String bbb;
  41. }
  42. @Mapper
  43. public interface UserMapper {
  44.  
  45.     UserDto userToNewUserDto(User user);
  46.     
  47.     //子集字段相同方法不用编写会自动生成
  48.     
  49.     //孙子集字段不相同(list会自动读取此方法生成list)
  50.     @Mapping(source = "ccc",target = "bbb")
  51.     SubSubUserDto bbb(SubSubUser subSubUser);
  52.  
  53. }
 

4.8.2 方式2

通过uses配置类型转换

 
  1. @Mapper(uses = {TestMapper.class})
  2. public interface UserMapper {
  3.     UserDto userToNewUserDto(User user);
  4. }
  5. @Mapper
  6. public interface TestMapper {
  7.     @Mapping(source = "ccc",target = "bbb")
  8.     SubSubUserDto bbb(SubSubUser subSubUser);
  9. }
 

5、获取 mapper

5.1 通过 Mapper 工厂获取

我们都是通过 Mappers.getMapper(xxx.class) 的方式来进行对应 Mapper 的获取。此种方法为通过 Mapper 工厂获取。

如果是此种方法,约定俗成的是在接口内定义一个接口本身的实例 INSTANCE, 以方便获取对应的实例。

 
  1. @Mapper
  2. public interface SourceMapper {
  3.  
  4.     SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  5.  
  6.     // ......
  7. }
 

这样在调用的时候,我们就不需要在重复的去实例化对象了。类似下面

Target target = SourceMapper.INSTANCE.source2target(source);

5.2 使用依赖注入

对于 Web 开发,依赖注入应该很熟悉。MapSturct 也支持使用依赖注入,同时也推荐使用依赖注入。

@Mapper(componentModel = "spring")

5.3 依赖注入策略

可以选择是通过构造方法或者属性注入,默认是属性注入。

 
  1. public enum InjectionStrategy {
  2.  
  3.     /** Annotations are written on the field **/
  4.     FIELD,
  5.  
  6.     /** Annotations are written on the constructor **/
  7.     CONSTRUCTOR
  8. }
 

类似如此使用

@Mapper(componentModel = "cdi" injectionStrategy = InjectionStrategy.CONSTRUCTOR)

5.4 自定义类型转换

有时候,在对象转换的时候可能会出现这样一个问题,就是源对象中的类型是Boolean类型,而目标对象类型是String类型,这种情况可以通过@Mapper的uses属性来实现:

 
  1. @Data
  2. public class User {
  3.     private Integer id;
  4.     private String name;
  5.     private String address;
  6.     private Date birth;
  7.     private Boolean isDisable;
  8. }
  9. @Data
  10. public class UserDto implements Serializable {
  11.     private Integer id;
  12.     private String name;
  13.     private String address;
  14.     private Date birth;
  15.     private String isDisable;
  16. }
  17. @Mapper(uses = {BooleanStrFormat.class})
  18. public interface UserMapper {
  19.     UserDto userToNewUserDto(User user);
  20. }
  21. public class BooleanStrFormat {
  22.     public String toStr(Boolean isDisable) {
  23.         if (isDisable) {
  24.             return "Y";
  25.         } else {
  26.             return "N";
  27.         }
  28.     }
  29.     public Boolean toBoolean(String str) {
  30.         if (str.equals("Y")) {
  31.             return true;
  32.         } else {
  33.             return false;
  34.         }
  35.     }
  36. }
 

要注意的是,如果使用了例如像spring这样的环境,Mapper引入uses类实例的方式将是自动注入,那么这个类也应该纳入Spring容器

 
  1. @Test
  2. public void userPoToUserDto() {
  3.     User user =new User();
  4.     user.setId(1);
  5.     user.setName("myx");
  6.     user.setAddress("江苏苏州");
  7.     user.setBirth(new Date());
  8.     user.setIsDisable(true);
  9.     SubUser subUser =new SubUser();
  10.     subUser.setDeleted(0);
  11.     subUser.setName("rkw");
  12.     UserMapper mapper = Mappers.getMapper(UserMapper.class);
  13.     UserDto userDto = mapper.userToNewUserDto(user);
  14.     System.out.println(userDto);
  15. }
posted @ 2024-10-24 14:28  CharyGao  阅读(204)  评论(0编辑  收藏  举报