MapStruct对象映射转换
前言
2024.05.26,项目中用到了MapStruct,今天对项目中的一个实体类进行改动,发现不起作用,一顿排查下来发现是MapStruct搞错的,因此打算系统整理一下MapStruct的用法。
介绍
在实际开发中我们经常需要做DTO、VO、Entity对象之间的转换,在开发中常见的做法有两种:
- 手动get、set
- 使用BeanUtils工具进行转换
手动进行set、get甚是麻烦,如果对象属性过多,则代码量很大;BeanUtils的对象转换工具是作用在运行期间,效率低下。
MapStruct是一款基于注解的Java的对象映射工具,与Lombok相似,作用于编译期间,编译后直接生成对应的class文件,因此运行期间的性能较高。
官网MapStruct – Java bean mappings, the easy way!
GitHub仓库GitHub - mapstruct/mapstruct: An annotation processor for generating type-safe bean mappers
MapStruct可以单独使用,也可以配合Spring框架一起使用
MapStruct实现对象之间转换的原理是基于get、set来实现的,来查看一下示例的生成的代码,非常简单
起步
- 导入依赖
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>
- 这是我们的类,此处使用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; }
- 定义一个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); }
- 示例
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生成的
在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); }
查看生成的代码
注意:在@Mapping
中不可以同时出现source
和expression
,因为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); }
踩坑点注意
-
修改了属性类型或新增、减少了属性,生成的实现类中并没有变化
解决方法:需要先手动删除maven打包后的文件,然后再重新启动程序即可
-
生成的对象中的属性全部为null,并且生成的实现类中并没有使用getter、setter
解释:由于lombok和MapStruct都是作用于编译期间,由于MapStruct和Lombok的工作顺序问题,MapStruct在Lombok之前执行,MapStruct检测此时的参数类型和返回值类型还没有getter、setter,导致没有去调用getter、setter
解决方式:调整pom.xml
中Lombok和MapStruct的引入顺序,要保证Lombok写在MapStruct之前
- 要完成集合元素类型之间的转换,为保证MapStruct能够正确生成目标对象,需要先定义单个对象转换的方法。
示例:要完成List<User>
到List<UserVo>
的转换,不可以在Mapper只定义这一个方法,也需要将单个元素之间的转换User
转换UserVo
的方法定义出来
正确示例:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!