MapStruct对象映射转换

前言

2024.05.26,项目中用到了MapStruct,今天对项目中的一个实体类进行改动,发现不起作用,一顿排查下来发现是MapStruct搞错的,因此打算系统整理一下MapStruct的用法。

介绍

在实际开发中我们经常需要做DTO、VO、Entity对象之间的转换,在开发中常见的做法有两种:

MapStruct可以单独使用,也可以配合Spring框架一起使用

MapStruct实现对象之间转换的原理是基于get、set来实现的,来查看一下示例的生成的代码,非常简单
image.png

起步

  1. 导入依赖
    org.mapstruct.mapstruct包含必要的注解,例如@Mapping
    org.mapstruct.mapstruct-processor中包含注解生成器的实现类

注意:因为Lombok会生成setter、getter,所以如果项目中使用了lombok,需要在pom.xml将lombok写在mapstruct的前面

<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct</artifactId>
		<version>1.5.5.Final</version>
</dependency>

<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct-processor</artifactId>
		<version>1.5.5.Final</version>
</dependency>
  1. 这是我们的类,此处使用lombok生成getter、setter,如果不使用lombok需要手动写getter、setter
    若源类型中的属性名和类型与目标类型中的相同,则可以自动完成转换,不需要额外的标注。
    User.java
@Data
public class User {
    private String userName;

    private String password;

    private String sex;

    private Integer age;
}

UserVo.java

@Data
public class UserVo {
    private String userName;

    private String sex;

    private Integer age;
}
  1. 定义一个Mapper接口
    接口中的方法就是要实现对象类型转换的方法
    注意:
  • 方法参数是源对象类型
  • 方法返回值类型是要转换成的目标对象类型
    在这个接口中我们定义了一个方法toUserVo,参数类型是源对象类型User,方法返回值类型是我们需要转换成的目标对象类型UserVo
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface ObjectConventor {

    UserVo toUserVo(User user);
    
}
  1. 示例
    public static void main(String[] args) {
        User user = new User();
        user.setUserName("XiaoMing");
        user.setAge(18);
        user.setSex("M");
        user.setPassword("123456");
        // 获取自动生成的映射器的实现类
        ObjectConventor mapper = Mappers.getMapper(ObjectConventor.class);
        // 调用对象类型转换方法
        UserVo userVo = mapper.toUserVo(user);
        System.out.println(userVo);
    }

在编译期间,会自动根据Mapper接口中的方法的参数和返回值类型自动生成实现类
我们使用Mappers.getMapper即可获取到指定的Mapper接口的实现类,调用方法即可完成对象之间的转换。

我们来看一下UserVo toUserVo(User user)这个方法的实现,就是利用setter、getter生成的
image.png

在Spring中使用

普通使用MapStruct我们需要手动获取mapper的实现类,但是在Spring中,我们可以直接从容器中通过依赖住的方式来获取Mapper的实现类。
只需要在Mapper注解中设置属性componentModel为字符串spring

@Mapper(componentModel = "spring")
public interface ObjectConventor {

    UserVo toUserVo(User user);

}

当然也可以使用MapStruct中内置的常量,如下所示

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ObjectConventor {

    UserVo toUserVo(User user);

}

在需要使用的地方通过@Autowired注入即可使用,示例

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    ObjectConventor objectConventor;

}

注意:@Mapper.component的值有多个,说两个常用的:

  • default:默认值,需要手动通过Mappers.getMapper()来获取mapper的实现类
  • spring:可以通过Spring容器依赖注入的方式获取mapper的实现类

常规使用

mapping

在MapStruct中,若两个类型中的属性名称相同,则会自动完成转换。
如果遇到对象类型的属性名称不一致,则可以利用@Mapping在接口方法上进行标注,
例如User.image要转换UserVo.avatarUrl
target用来指定要处理的目标类型的属性,source用来指定源类型的属性名

@Mapper
public interface ObjectConventor {

    @Mapping(target = "avatarUrl", source = "image")
    UserVo toUserVo(User user);

}

mapping.expression

如果属性名称相同,但属性类型不相同:

  • 如果是基本数据类型和包装类之间转换,mapstruct会进行拆箱、装箱,我们不需要处理
  • 包装类与String之间的转换,mapStruct自动处理,不需要处理
  • 日期与String之间的转换
    除了以上情况之外的类型不相同,我们需要借助@Mapping.expression手动进行处理,
    使用@Mapping.expression可以完成对目标属性的特殊处理
    例如:User.address是Object类型,要转换为UserVo.address为String类型
@Mapper
public interface ObjectConventor {

    @Mapping(target = "avatarUrl", source = "image")
    @Mapping(target = "address", expression = "java(source.getAddress().getProvince() + source.getAddress().getCity() )")
    UserVo toUserVo(User source);

}

查看生成的代码
image.png

注意:在@Mapping中不可以同时出现sourceexpression,因为MapStruct会把整个express作为整体去xxx.setTarget(expression)执行

常量赋值

如果要给对象中的属性赋值常量,则可以使用constant属性
注意:constant与source不可同时出现

@Mapper
public interface ObjectConventor {

    @Mapping(target = "avatarUrl", source = "image")
    @Mapping(target = "country", constant = "China")
    UserVo toUserVo(User source);

}

空值处理

当source中的属性为null时,可以我target赋值默认值

  • defaultValue默认值
  • defaultExpression默认表达式
@Mapper
public interface ObjectConventor {

    @Mapping(target = "avatarUrl", source = "image")
    @Mapping(target = "sex", source = "sex", defaultValue = "未知")
    @Mapping(target = "status", source = "disable", defaultExpression = "java( com.demo.utils.UserUtils.getDefaultStatus() )")
    UserVo toUserVo(User source);

}

@Mappings

一个方法上有多个Mapping,可以统一放到@Mappings中,

@Mapper
public interface ObjectConventor {


    @Mappings({
            @Mapping(target = "avatarUrl", source = "image"),
            @Mapping(target = "sex", source = "sex", defaultValue = "未知"),
            @Mapping(target = "status", source = "disable", defaultExpression = "java( com.demo.utils.UserUtils.getDefaultStatus() )")
    })
    UserVo toUserVo(User source);

}

嵌套类型处理

通过属性.属性的方式即可

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ObjectConventor {

    @Mapping(target = "country", source = "address.country")
    UserVo toUserVo(User source);
}

如果是复杂的嵌套类型,则推荐额外再定义一个类型转换方法,MapStruct会自动去调用

SpringBean注入

在Mapper中注入Spring Bean,实现在mapper调用SpringBean去处理
由于interfac无法实现实现依赖注入,因此需要修改Mapper为抽象类abstract class,所有的方法也都需要修改成abstract

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public abstract class ObjectConventor {

    @Autowired
    UserService userService;

    @Mapping(target = "roles", expression = "java( userService.getRoles(source.getUserName()) )")
    abstract UserVo toUserVo(User source);

}

自定义映射逻辑

由于Java8中的interface支持default方法,因此可以在Mapper中自定义映射逻辑
示例:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ObjectConventor {

    default UserVo toUserVo(User user) {
        UserVo userVo = new UserVo();
        userVo.setUserName(user.getUserName());
        userVo.setAge(user.getAge());
        return userVo;
    }
    
}

日期格式化

Date与String之间的转换,可以规定日期格式
生成的代码会自动去调用SimpleDateFormat去格式化日期

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ObjectConventor {

    @Mapping(target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss")
    UserVo toUserVo(User source);
}

踩坑点注意

  1. 修改了属性类型或新增、减少了属性,生成的实现类中并没有变化
    解决方法:需要先手动删除maven打包后的文件,然后再重新启动程序即可
    image.png

  2. 生成的对象中的属性全部为null,并且生成的实现类中并没有使用getter、setter
    image.png

解释:由于lombok和MapStruct都是作用于编译期间,由于MapStruct和Lombok的工作顺序问题,MapStruct在Lombok之前执行,MapStruct检测此时的参数类型和返回值类型还没有getter、setter,导致没有去调用getter、setter

解决方式:调整pom.xml中Lombok和MapStruct的引入顺序,要保证Lombok写在MapStruct之前
image.png

  1. 要完成集合元素类型之间的转换,为保证MapStruct能够正确生成目标对象,需要先定义单个对象转换的方法。
    示例:要完成List<User>List<UserVo>的转换,不可以在Mapper只定义这一个方法,也需要将单个元素之间的转换User转换UserVo的方法定义出来
    正确示例:
    image.png
posted @ 2024-05-26 16:52  秋天Code  阅读(1483)  评论(0编辑  收藏  举报