Spring Boot整合mapstruct(简化实体类之间的转换)

在日常开发中,经常会对实体类进行转换,比如User在进行用户添加的时候会使用UserDto,相对应的在查询后的实体可能是UserVo,针对这些Dto和Vo的转换我们一般需要直接定义一个或多个实体类,并且要多个转换实体类的方法,以下拿SimpleSource和SimpleDestination举例 比如:

java
复制代码
public class SimpleSource { private String name; private String description; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
java
复制代码
public class SimpleDestination { private String name; private String description; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }

如上这两个类,我想要SimpleSource转换为SimpleDestination需要写一个转换实体类的方法

java
复制代码
public SimpleDestination sourceToDestination(SimpleSource source) { SimpleDestination simpleDestination = new SimpleDestination(); simpleDestination.setName(source.getName()); simpleDestination.setDescription(source.getDescription()); return simpleDestination; }

需要写类似这样的方法,也可以直接使用BeanUtil.copyProperties这样的工具类 会针对这两个实体类中相同变量名的变量进行复制 但是感觉写多了 还是不够优雅 如果有多个实体类 多个转换规则 变量名不一样 变量的类型也不一样 处理起来其实和上面的效果是差不多的 有没有更好的办法呢

java
复制代码
public SimpleDestination sourceToDestination(SimpleSource source) { SimpleDestination simpleDestination = new SimpleDestination(); BeanUtil.copyProperties(source, simpleDestination); return simpleDestination; }

接下来介绍mapstruct

MapStruct 在编译时生成 bean 映射代码,从而确保高性能,允许开发人员快速发现并及时解决问题。

具体流程为 定义一个source实体 一个target实体 定义一个转换器 使用的使用直接使用转换器source实体转换target实体 但是属性转换的代表不需要我们编写 mapstruct 在编译期间 会直接生成好转换的一系列代码

如上面的

java
复制代码
public SimpleDestination sourceToDestination(SimpleSource source) { SimpleDestination simpleDestination = new SimpleDestination(); simpleDestination.setName(source.getName()); simpleDestination.setDescription(source.getDescription()); return simpleDestination; }

这个是 mapstruct 在编译期间直接生成的代码内容

前置准备:

java
复制代码
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>17</java.version> <mapstruct.version>1.6.3</mapstruct.version> <lombok.version>1.18.38</lombok.version> </properties>

锁定依赖版本

java
复制代码
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${mapstruct.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency>

由于是编译期间生成的内容 所以通过maven打包的时候 需要使用插件

java
复制代码
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <parameters>true</parameters> <source>${java.version}</source> <target>${java.version}</target> <encoding>${project.build.sourceEncoding}</encoding> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.3.0</version> </plugin> </plugins> </build>

上文讲到了转换器 转换器到底是什么呢 在mapstruct中转换器可以是一个接口也可以是一个抽象类 但是在Spring环境中 要想注入Bean只能是抽象类 下面直接使用抽象类 接口就暂时掠过 并且针对转换器必须指定Spring环境

java
复制代码
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SystemDomainConverterMapper { }

准备基本上完毕 这里提醒检查一下依赖是否全部都在

如何使用?

准备两个实体类 上文直接使用了lombok 我们就直接使用lombok生成get set 方法以及全参无参构造方法

java
复制代码
@Data @AllArgsConstructor @NoArgsConstructor public class SimpleSource { private String name; private String description; }
java
复制代码
@Data @AllArgsConstructor @NoArgsConstructor public class SimpleDestination { private String name; private String description; }

然后在转换器中增加实体类转换的方法

java
复制代码
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SystemDomainConverterMapper { /** * source实体字段 和 target实体字段 一致 */ public abstract SimpleDestination sourceToDestination(SimpleSource source); }

在后续操作中 直接把这个当作一个bean使用即可

java
复制代码
@Autowired private SystemDomainConverterMapper systemDomainConverterMapper;

注入到Spring上下文中

注意转换器必须标注是Spring环境

java
复制代码
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

创建一个单元测试

java
复制代码
@SpringBootTest public class MapstructTest { @Autowired private SystemDomainConverterMapper systemDomainConverterMapper; @Test public void mapstructTest() { SimpleSource simpleSource = new SimpleSource("张三", "长方脸"); SimpleDestination simpleDestination = systemDomainConverterMapper.sourceToDestination(simpleSource); System.out.println(JSONUtil.toJsonStr(simpleDestination)); } }

控制台输出

java
复制代码
{"name":"张三","description":"长方脸"}

相对来说还是比较优雅 节省了很多代码

如果测试报错什么的 清理一下spring boot 的target 然后重新mvn clean install 如果生成成功在下面路径下能看到生成的转换器的实现类

java
复制代码
\target\generated-sources\annotations
java
复制代码
import javax.annotation.processing.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2025-07-11T15:16:37+0800", comments = "version: 1.6.3, compiler: javac, environment: Java 21.0.5 (Amazon.com Inc.)" ) @Component public class SystemDomainConverterMapperImpl extends SystemDomainConverterMapper { @Override public SimpleDestination sourceToDestination(SimpleSource source) { if ( source == null ) { return null; } SimpleDestination simpleDestination = new SimpleDestination(); simpleDestination.setName( source.getName() ); simpleDestination.setDescription( source.getDescription() ); return simpleDestination; } }

原理其实和自己手写是一样的 但是能生成何乐而不为呢

如果source实体类和target实体类 字段名不一样 改怎么生成呢?

方便测试 直接将description字段和name进行互换一下 source实体类的description 映射成 target实体类中的 name 并且 source实体类的name 映射成 target实体类中的 description

在转换器中新增方法

java
复制代码
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SystemDomainConverterMapper { /** * target实体字段 和 source实体字段 不一致 */ @Mapping(target = "name", source = "source.description") @Mapping(target = "description", source = "source.name") public abstract SimpleDestination sourceConverterDestination(SimpleSource source); }

对应的单元测试

java
复制代码
@SpringBootTest public class MapstructTest { @Autowired private SystemDomainConverterMapper systemDomainConverterMapper; @Test public void mapstructTest() { SimpleSource simpleSource = new SimpleSource("张三", "长方脸"); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceToDestination(simpleSource); SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterDestination(simpleSource); System.out.println(JSONUtil.toJsonStr(simpleDestination)); } }

控制台输出结果

java
复制代码
{"name":"长方脸","description":"张三"}

可以看到sources实体类和target实体类的字段已经互换了

如果想要自动填充Spring Bean中配置文件的值怎么办?

mapstruct也提供了对应的方法

在转换器中新增一个方法

java
复制代码
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SystemDomainConverterMapper { /** * 权限修饰符必须是 public */ @Autowired public RuoYiConfig ruoyiConfig; /** * source实体想要调用某个bean的方法生成target中指定字段内容 */ @Mapping(target = "name", expression = "java(ruoyiConfig.getName())") @Mapping(target = "description", source = "source.name") public abstract SimpleDestination sourceConverterRuoyiDestination(SimpleSource source); }

我们需要将bean注入到转换器中 并且权限修饰符必须是public 这里使用的是Ruoyi-Vue的Spring Boot中的RuoYiConfig配置文件 将RuoYiConfig中的name 注入到target实体类的name中

对应的单元测试

java
复制代码
@SpringBootTest public class MapstructTest { @Autowired private SystemDomainConverterMapper systemDomainConverterMapper; @Test public void mapstructTest() { SimpleSource simpleSource = new SimpleSource("张三", "长方脸"); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceToDestination(simpleSource); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterDestination(simpleSource); SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterRuoyiDestination(simpleSource); System.out.println(JSONUtil.toJsonStr(simpleDestination)); } }

控制台输出

java
复制代码
{"name":"RuoYi","description":"张三"}

如果source填充时候某一个字段为NULL 想要给一个默认值怎么办?能不能直接调用Java中的某一个工具类 填充值?

可以!!

在转换器中新增一个方法

java
复制代码
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SystemDomainConverterMapper { /** * 如果source实体的某一个字段为null(空字符串不行) 想要调用某个类的方法生成target中指定字段内容 需要指定具体类的全类路径名 */ @Mapping(target = "name", source = "source.name", defaultExpression = "java(cn.hutool.core.lang.UUID.randomUUID().toString())") @Mapping(target = "description", source = "source.description") public abstract SimpleDestination sourceConverterHutoolDestination(SimpleSource source); }

这里如果source实体类的name为空 使用hutool的UUID工具类生成一个UUID

对应的单元测试

先测试name不为空的情况

java
复制代码
@SpringBootTest public class MapstructTest { @Autowired private SystemDomainConverterMapper systemDomainConverterMapper; @Test public void mapstructTest() { SimpleSource simpleSource = new SimpleSource("张三", "长方脸"); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceToDestination(simpleSource); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterDestination(simpleSource); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterRuoyiDestination(simpleSource); SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterHutoolDestination(simpleSource); System.out.println(JSONUtil.toJsonStr(simpleDestination)); } }

控制台输出

java
复制代码
{"name":"张三","description":"长方脸"}

测试name为空的情况

java
复制代码
@SpringBootTest public class MapstructTest { @Autowired private SystemDomainConverterMapper systemDomainConverterMapper; @Test public void mapstructTest() { SimpleSource simpleSource = new SimpleSource(null, "长方脸"); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceToDestination(simpleSource); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterDestination(simpleSource); //SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterRuoyiDestination(simpleSource); SimpleDestination simpleDestination = systemDomainConverterMapper.sourceConverterHutoolDestination(simpleSource); System.out.println(JSONUtil.toJsonStr(simpleDestination)); } }

控制台输出

java
复制代码
{"name":"454ae0b5-b1b0-44db-be95-059e22594b5e","description":"长方脸"}

可以看到如果name为空直接生成了一个UUID填充

整体用下来 还不错 减少很多无用代码 但是每次修改转换器都得重新打包 生成新的转换器实现类 但是如果依赖很多整体的时间还是挺长的 但是用喝一口水的时间换很多无用代码的编写 我还是觉得不错的

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP