从0开始搭建后台管理系统,这是No13。上一篇我们讲到了:Mybatis-Plus配置,实现多租户、逻辑删除、乐观锁。这一节,我们就来引入MapStruct。
MapStruct是一个 编译时的Java对象映射框架 ,通过注解处理器在编译阶段生成类型安全、高性能的映射代码,主要用于解决不同Java对象(如DTO与实体类、VO与BO等)之间的属性转换问题。
为什么引入MapStruct
- 高性能(编译时生成代码)
MapStruct在 编译阶段 就生成完整的Java映射代码,避免了运行时反射(如ModelMapper、Dozer等框架的反射开销),执行效率接近手写的映射代码。 - 类型安全(编译时错误检查)
- 映射错误在 编译阶段 即可发现(如字段名不匹配、类型不兼容等),避免运行时异常。
- IDE会实时提示映射问题,支持代码补全和跳转。
对比 :反射式框架(如ModelMapper)在运行时才会发现字段不匹配,增加调试难度。
- 配置灵活(注解驱动)
提供丰富的注解支持复杂映射场景:
- 字段名映射: @Mapping(source = “userId”, target = “id”)
- 类型转换:内置基本类型(如String↔Date、Enum↔String)转换,支持自定义转换器。
- 嵌套对象映射:自动处理嵌套对象的转换(如 User.address.city → UserDTO.city )。
- 集合映射:支持List、Set、Map等集合类型的批量转换。
- 易于调试与维护
- 生成的代码是标准Java代码,可直接阅读和调试(在 target/generated-sources 目录下)。
- 映射逻辑集中在Mapper接口中,便于维护和重构。
- 低运行时依赖
MapStruct核心依赖仅为 mapstruct (API)和 mapstruct-processor (编译时处理器),运行时无额外依赖,避免jar包冲突。
引入MapStruct
加入如下依赖
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>
<version>${mapstruct.version}</version>
</dependency>
使用@AutoMapper自动转换视图对象
@Data
@AutoMapper(target = SysUser.class)
public class SysUserVo implements Serializable {
}
使用@AutoMapper自动转换业务对象
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysUser.class, reverseConvertGenerate = false)
public class SysUserBo extends BaseEntity{
@Serial
private static final long serialVersionUID = 1L;
}
转换工具类
package cn.lovecto.yuen.common.utils;
import java.util.List;
import java.util.Map;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import io.github.linpeilie.Converter;
public class ConvertUtils {
private final static Converter MAPSTRUCT_CONVERTER = SpringUtils.getBean(Converter.class);
/**
* 转换对象
*
* @param source 源对象
* @param desc 目标对象类型
* @param <T> 源对象类型
* @param <V> 目标对象类型
* @return 目标对象
*/
public static <T, V> V convert(T source, Class<V> desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
return MAPSTRUCT_CONVERTER.convert(source, desc);
}
/**
* 转换对象
*
* @param source 源对象
* @param desc 目标对象
* @param <T> 源对象类型
* @param <V> 目标对象类型
* @return 目标对象
*/
public static <T, V> V convert(T source, V desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
return MAPSTRUCT_CONVERTER.convert(source, desc);
}
/**
* 转换对象列表
*
* @param sourceList 源对象列表
* @param desc 目标对象类型
* @param <T> 源对象类型
* @param <V> 目标对象类型
* @return 目标对象列表
*/
public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) {
if (ObjectUtil.isNull(sourceList)) {
return null;
}
if (CollUtil.isEmpty(sourceList)) {
return CollUtil.newArrayList();
}
return MAPSTRUCT_CONVERTER.convert(sourceList, desc);
}
/**
* 转换Map对象
*
* @param map 源Map对象
* @param beanClass 目标对象类型
* @param <T> 目标对象类型
* @return 目标对象
*/
public static <T> T convert(Map<String, Object> map, Class<T> beanClass) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(beanClass)) {
return null;
}
return MAPSTRUCT_CONVERTER.convert(map, beanClass);
}
}
若出现类似报错:
io.github.linpeilie.ConvertException: cannot find converter from SysUser to SysUserVo
则在主项目的pom.xml中加入
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc-scribe</artifactId>
<version>0.15.0</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot}</version>
</path>
<path>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${mapstruct.lombok.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
写个测试试试:
package cn.lovecto.yuen_app;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import cn.lovecto.yuen.YuenAppApplication;
import cn.lovecto.yuen.common.utils.ConvertUtils;
import cn.lovecto.yuen.system.domain.SysUser;
import cn.lovecto.yuen.system.domain.vo.SysUserVo;
import cn.lovecto.yuen.system.mapper.SysUserMapper;
@SpringBootTest(classes = YuenAppApplication.class)
public class ConvertUtilsTest {
@Autowired
private SysUserMapper userMapper;
@Test
public void convert() {
try {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUserName, "admin");
List<SysUser> users = userMapper.selectList(wrapper);
for(SysUser u : users) {
SysUserVo vo = ConvertUtils.convert(u, SysUserVo.class);
System.out.println(vo.getUserName());
}
List<SysUserVo> vos = ConvertUtils.convert(users, SysUserVo.class);
for(SysUserVo vo : vos) {
System.out.println(vo.getUserName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
LoveCTO

