当前位置: 首页 > news >正文

mapStruct实体类属性映射工具实现

mapStruct实体类属性映射实现

  • 1. 概述
    • 对比 BeanUtils.copyProperties
    • 场景对比
  • 快速入门
    • lombok + mapStruct
      • maven 依赖
      • 代码实现
        • 1. 定义两个要转换的实体类
        • 2. 定义转化接口
        • 测试
    • mapStruct + Spring
      • 1. 注册成bean
      • 需要使用bean
    • 转化规则
      • 1. 成员变量名不同时
      • 2. 子对象映射
      • 3. 数据类型映射
        • 自定义映射
      • 4. 多对象映射
      • 5. 原对象改造

1. 概述

MapStruct 是一个 Java Bean mapper,用于Java Bean 之间的转换。MapStruct 基于约定优于配置的设计思想,相较于常用的 BeanUtils.copyProperties 它更高效、优雅。
mapStruct 官方网站
官方文档

对比 BeanUtils.copyProperties

  1. 性能优势​
    使用 MapStruct,我们只需要定义映射接口,该库在编译时会自动生成具体实现代码, 生成的代码和手动编写的属性拷贝代码效率一致,​​适合高频调用场景​​(如高并发、大数据量)。。
  2. 类型安全与编译时检查​
  • ​​MapStruct​​:
    ​​ 强类型映射​​:在编译时检查属性名和类型是否匹配,​​避免运行时错误​​
    ​​ 自动提示错误​​:如果源对象或目标对象的属性名/类型不匹配,编译时会直接报错。
  • BeanUtils.copyProperties()​​:
    ​​ 弱类型检查​​:属性名匹配但类型不兼容时(如 String 复制到 Integer),​​运行时抛出异常​​,增加调试成本。
  1. 复杂映射支持​
  • ​​MapStruct​​:
    • ​​自定义转换逻辑​​:支持通过注解或自定义方法实现复杂类型转换(如 Date 转 String、嵌套对象映射)。
    • 多源参数映射​​:支持从多个源对象合并属性到目标对象。
    • 条件映射​​:可配置条件判断是否复制某个属性。

场景对比

​​场景​​​​MapStruct​​​​BeanUtils.copyProperties()​​
高频调用✅ 适合(高性能)❌ 不适用(反射开销大)
简单属性复制✅ 适合(但需要定义接口)✅ 适合(代码简洁)
复杂类型转换✅ 适合(支持自定义逻辑)❌ 不适用
需要编译时检查✅ 适合❌ 不适用
快速原型开发❌ 略繁琐(需定义接口)✅ 适合(快速实现)

快速入门

lombok + mapStruct

maven 依赖

  1. 映入lombok、mapStruct 依赖
  2. 必须搭配lombok-mapstruct-binding插件,以及mapStrust-process
  3. 加入path配置打包工具
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><org.mapstruct.version>1.6.3</org.mapstruct.version><org.projectlombok.version>1.18.30</org.projectlombok.version><lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties><dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency><!-- lombok dependencies should not end up on classpath --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version><scope>provided</scope></dependency><!-- IntelliJ pre 2018.1.1 requires the mapstruct processor to be present as provided dependency -->
<!--        <dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version><scope>provided</scope></dependency>--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope><version>4.13.1</version></dependency></dependencies><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><!-- See https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html --><!-- Classpath elements to supply as annotation processor path. If specified, the compiler   --><!-- will detect annotation processors only in those classpath elements. If omitted, the     --><!-- default classpath is used to detect annotation processors. The detection itself depends --><!-- on the configuration of annotationProcessors.                                           --><!--                                                                                         --><!-- According to this documentation, the provided dependency processor is not considered!   --><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>${lombok-mapstruct-binding.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins></pluginManagement></build>

代码实现

1. 定义两个要转换的实体类
@Data
public class SourceBo {private String test;
}@Data
public class TargetBo {private Long testing;}
2. 定义转化接口
import com.example.webapp.demos.entity.mapStruct.SourceBo;
import com.example.webapp.demos.entity.mapStruct.TargetBo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;/*** @Description: 类型转换。* 注意:并不是只能有一个转换的方法,而是我这只写了一个。*/
@Mapper
public interface SourceTargetMapper {SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );@Mapping( target = "testing", source = "test" )TargetBo toTarget( SourceBo s );
}
测试
import com.example.webapp.demos.Config.SourceTargetMapper;
import com.example.webapp.demos.entity.mapStruct.SourceBo;
import com.example.webapp.demos.entity.mapStruct.TargetBo;
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.assertEquals;/**
* @ClassName : SimpleTest  
* @Description :   
* @Date: 2025/6/2  13:27
*/public class SimpleTest {@Testpublic void testMapping() {SourceBo s = new SourceBo();s.setTest( "5" );TargetBo t = SourceTargetMapper.MAPPER.toTarget( s );assertEquals( 5, (long) t.getTesting() );}
}

嗯,没错,这样就可以了,是不是so easy!

实现原理呢,同学们可以关注一个 target 目录下生成的.class文件,它自动生成了我们定义接口的实现类。
在这里插入图片描述

如果你的代码出现和lombok的冲突问题。 官网解决方案

mapStruct + Spring

或许你会觉得使用 SourceTargetMapper.MAPPER.xxxx() 不够优雅或者与你使用spring 的bean风格有点融洽,那么你也可以将 SourceTargetMapper 注入spring 容器,来实现代码风格的统一。

1. 注册成bean

那么改动也很简单,修改@Mapper注解,设置componentModel属性值为spring。

@Mapper(componentModel = "spring")
public interface SourceTargetMapper{}

测试类

@Autowiredprivate SourceTargetMapper sourceTargetMapper;@Testpublic void testMapping() {SourceBo s = new SourceBo();s.setTest( "5" );TargetBo t = sourceTargetMapper.toTarget( s );assertEquals( 5, (long) t.getTesting() );}

需要使用bean

反过来,我们需要在mapper中引用Spring容器中的组件该如何实现? 这种情况下,我们需要改用抽象类而非接口了:

@Mapper(componentModel = "spring")
public abstract class SimpleDestinationMapperUsingInjectedService {@Autowiredprotected SimpleService simpleService;@Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")public abstract SimpleDestination sourceToDestination(SimpleSource source);
}

切记不要将注入的 bean 设置为private ,因为 MapStruct 需要在生成的实现类中访问该对象

编译后,代码就相当于于

@Component  // 因为 componentModel = "spring"
public class SimpleDestinationMapperUsingInjectedServiceImpl extends SimpleDestinationMapperUsingInjectedService {@Overridepublic SimpleDestination sourceToDestination(SimpleSource source) {SimpleDestination destination = new SimpleDestination();// 关键行:调用 simpleService 加工 namedestination.setName(simpleService.enrichName(source.getName()));return destination;}
}

转化规则

1. 成员变量名不同时

public class EmployeeDTO {private int employeeId;private String employeeName;// getters and setters
}public class Employee {private int id;private String name;// getters and setters
}@Mapper
public interface EmployeeMapper {@Mapping(target = "employeeId", source = "entity.id")@Mapping(target = "employeeName", source = "entity.name")EmployeeDTO employeeToEmployeeDTO(Employee entity);@Mapping(target = "id", source = "dto.employeeId")@Mapping(target = "name", source = "dto.employeeName")Employee employeeDTOtoEmployee(EmployeeDTO dto);
}

2. 子对象映射

这里我们需要为 Division 与 DivisionDTO 之间的转换添加方法。如果MapStruct检测到需要转换对象类型,并且要转换的方法存在于同一个类中,它将自动使用它。

public class EmployeeDTO {private int employeeId;private String employeeName;private DivisionDTO division;// getters and setters omitted
}public class Employee {private int id;private String name;private Division division;// getters and setters omitted
}
public class Division {private int id;private String name;// default constructor, getters and setters omitted
}public class DivisionDTO {private int Did;private String name;// default constructor, getters and setters omitted
}
public interface SourceTargetMapper {SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );@Mapping( target = "testing", source = "test" )TargetBo toTarget( SourceBo s );/*** * 如果写 Did 编译无法通过*/@Mapping(target = "did",source = "id")DivisionDTO toDivisionDTO( Division s );
}

这个还是有点小问题的,如果写 Did 编译无法通过

3. 数据类型映射

MapStruct还提供了一些开箱即用的隐式类型转换。本例中,我们希望将 String 类型的日期转换为 Date 对象。

有关隐式类型转换的更多详细信息,请查看 MapStruct 官方文档

public class Employee {// other fieldsprivate Date startDt;// getters and setters
}
public class EmployeeDTO {// other fieldsprivate String employeeStartDt;// getters and setters
}@Mapping(target="employeeId", source = "entity.id")
@Mapping(target="employeeName", source = "entity.name")
@Mapping(target="employeeStartDt", source = "entity.startDt",dateFormat = "dd-MM-yyyy HH:mm:ss")
EmployeeDTO employeeToEmployeeDTO(Employee entity);@Mapping(target="id", source="dto.employeeId")
@Mapping(target="name", source="dto.employeeName")
@Mapping(target="startDt", source="dto.employeeStartDt",dateFormat="dd-MM-yyyy HH:mm:ss")
Employee employeeDTOtoEmployee(EmployeeDTO dto);
自定义映射

一些特殊场景 @Mapping 无法满足时,我们需要定制化开发,同时希望保留MapStruct自动生成代码的能力。那我们可以通过创建抽象类实现。

@Mapper
abstract class TransactionMapper {public TransactionDTO toTransactionDTO(Transaction transaction) {TransactionDTO transactionDTO = new TransactionDTO();transactionDTO.setUuid(transaction.getUuid());// BigDecimal 与 long 的自定义转化transactionDTO.setTotalInCents(transaction.getTotal().multiply(new BigDecimal("100")).longValue());return transactionDTO;}public abstract List<TransactionDTO> toTransactionDTO(Collection<Transaction> transactions);
}

Collection 与 List 类型之间的转换,仍然交给 MapStruct 完成,我们只需定义接口。

又或者

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ProductMapper {@Mapping(source = "length",target = "length",qualifiedByName = "cmToM")@Mapping(source = "width",target = "width",qualifiedByName = "cmToM")@Mapping(source = "high",target = "high",qualifiedByName = "cmToM")Product productVOToPrduct(ProductVO productVO);@Named("cmToM")default BigDecimal cmToM (BigDecimal oldValue){if (oldValue == null) {return BigDecimal.ZERO;}return oldValue.divide(new BigDecimal("100"));}
}

4. 多对象映射

@Mappings({@Mapping(source ="userVO.name",target = "username"),@Mapping(source ="score.score",target = "score")
})
User userVOToUser(UserVO userVO, Score score);

5. 原对象改造

    @Mapping( target = "t.testing", source = "s.test" )void toTarget( SourceBo s,@MappingTarget TargetBo t );

相关文章:

  • 【Python 进阶3】常见的 call 和 forward 区别
  • 【知识点】第3章:基本数据类型
  • Aviator表达式语法基础和Java实战表达式(电商应用)
  • MDP中的events部分
  • leetcode0513. 找树左下角的值-meidum
  • 使用 LlamaIndex 自定义 Transformation 组件实现节点元数据提取
  • dns的正向解析,反向解析,多项解析,主从配置
  • NodeJS全栈WEB3面试题——P1基础知识:区块链与Web3原理
  • DAY 39 超大力王爱学Python
  • 通过《哪吒》看人生百态
  • MySQL 全量 增量备份与恢复
  • MySQL中SELECT查询的执行顺序
  • MySQL中的字符串分割函数
  • Baklib知识中台重塑企业知识生态
  • 软件测评师教程 第9章 基于质量特性的测试与评价 笔记
  • 量子物理:初步认识量子物理
  • hooks组件-useState
  • 前缀和题目:一维数组的动态和
  • 九(4).存在指针的引用,不存在引用的指针
  • RNN循环网络:给AI装上“记忆“(superior哥AI系列第5期)
  • wordpress是是什么技术/优化网络的软件
  • 做外贸去哪些网站找老外/黄页推广2021
  • 台商网站建设公司黄页/山西搜索引擎优化
  • 公众号运营地区要真实吗/seo修改器
  • 无锡电商网站设计/谷歌外贸
  • 网站制作 维护 武汉/广告推广方式有哪几种