SpringBoot集成MapStruct实现类型转换
在分层架构的开发中,经常遇到以下场景:将 DAO 查询出的 Entity 对象,转换为返回给前端的 VO 对象;或者将前端传来的 DTO 对象,转换为要存入数据库的 Entity 对象。
每当两个对象的字段很多时,用get和set的方法完成“数据搬运”的过程就比较麻烦,接下来介绍一下使用MapStruct的方法。
什么是 MapStruct?
MapStruct是一款强大的JavaBean映射框架,它的目标是简化JavaBean之间的映射过程。与手动编写繁琐的转换代码相比,MapStruct通过使用注解和自动生成的代码,提供了一种高效、类型安全、可维护的解决方案。
MapStruct 是一个 Java 注解处理器,用于在编译时自动生成类型安全、高性能的对象映射代码。 请注意这里的几个关键词:
编译时生成代码:它不是像 `BeanUtils.copyProperties` 那样在运行时使用反射,所以性能极高,与你手写的 `get/set` 代码完全一样。
类型安全:它在编译期间就会对字段类型进行检查。如果源对象和目标对象的字段类型不兼容,编译会直接报错,让你能尽早发现问题。
注解驱动:你只需要定义一个简单的 Java 接口并添加注解,MapStruct 就会在幕后为你完成所有繁重的工作。
MapStruct的主要特性包括:
- 自动生成映射代码: 通过注解,MapStruct能够在编译期自动生成映射代码,避免了手动编写大量重复的转换逻辑。
- 类型安全: MapStruct通过编译时检查,确保了映射的类型安全性,减少了在运行时发生的类型错误。
- 可配置性: 支持通过注解进行高度的配置,满足各种复杂映射需求。
- 高性能: 自动生成的代码经过优化,性能接近手写的映射代码,同时支持缓存策略,提高了映射的执行效率。
在Spring Boot应用中,数据的处理和转换是常见的任务。而MapStruct作为一个优秀的映射框架,为Spring Boot应用提供了以下优势:
- 简化开发流程: 使用MapStruct可以大大减少手动编写转换逻辑的工作,使开发者能够更专注于业务逻辑的实现。
- 类型安全: Spring Boot注重类型安全,而MapStruct在编译期间就能够检查出潜在的类型问题,提高了代码的质量。
- 维护性: 自动生成的映射代码结构清晰,易于维护。当实体类发生变化时,MapStruct会自动更新映射代码,减少了手动维护的成本。
- 性能优势: 自动生成的映射代码经过优化,性能接近手写的映射代码。在大规模数据转换的场景下,MapStruct能够提供较好的性能表现。
MapStruct的特性:
- 注解支持: MapStruct使用@Mapper注解标识接口,通过在接口的抽象方法上添加@Mapping等注解来配置映射规则。
- 编译时生成代码: MapStruct在编译时生成映射代码,消除了运行时的性能开销,同时提高了代码的类型安全性。
- 类型转换支持: 支持各种基本数据类型、集合类型以及自定义类型之间的转换。
- 灵活的配置: 提供丰富的配置选项,允许开发者自定义映射行为,满足复杂业务场景的需求。
Spring Boot项目集成MapStruct
引入MapStruct依赖
首先,在Spring Boot项目中引入MapStruct依赖。在pom.xml文件中添加以下依赖:
<!-- MapStruct 核心库 实现 Java 对象映射 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.1.Final</version>
</dependency>
<!-- mapstruct包含了运行时需要的类,而mapstruct-processor是注解处理器,用于在编译时生成映射代码 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</dependency>
这里使用了mapstruct和mapstruct-processor两个依赖。mapstruct包含了运行时需要的类,而mapstruct-processor是注解处理器,用于在编译时生成映射代码。
新建实体类(PO 和 BO)
package com.demo.springboot.model.po; import lombok.Data; import java.util.Date; /** * @Data 注解会自动生成 Getter、Setter、equals、hashCode、toString 等方法,可以大大简化类的编写,减少了样板代码。 */ @Data public class StudentPO { private Integer id; private String name; private Integer age; private String sex; private Date birthday; }
package com.demo.springboot.model.bo; import lombok.Data; import java.util.Date; /** * @Data 注解会自动生成 Getter、Setter、equals、hashCode、toString 等方法,可以大大简化类的编写,减少了样板代码。 */ @Data public class StudentBO { private Integer id; private String name; private Integer age; private String sex; private Date birthday; }
创建 Mapper 接口
现在需要定义“翻译规则”。你只需要创建一个接口,剩下的交给 MapStruct。
package com.demo.springboot.converter; import com.demo.springboot.model.bo.StudentBO; import com.demo.springboot.model.po.StudentPO; import org.apache.ibatis.jdbc.Null; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import java.util.List; /** * MapStruct 是一个 Java 注解处理器,用于在编译时自动生成类型安全、高性能的对象映射代码 * 只需要创建一个接口,剩下的交给 MapStruct * 通过设置 componentModel = “spring”,mapstruct 会在生成的实现类上添加 @Component注解,使其成为 Spring 管理的 Bean,从而可以在其他组件中通过依赖注入方式使用 */ @Mapper(uses = {Null.class}, componentModel = "spring") public interface StudentConverter { /** * 将单个 DTO 转换为实体。 * MapStruct 会自动映射所有同名、同类型的字段。 * 我们只需为有特殊转换需求的字段(如日期格式)添加规则。 * @param dto 源 DTO 对象 * @return 转换后的实体对象 */ @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd'T'HH:mm:ss") StudentBO toDataProduct(StudentPO dto); /** * 将 DTO 列表转换为实体列表。 * MapStruct 会自动复用上面的 toEntity 方法来处理列表中的每一个元素。 * * @param dtoList 源 DTO 列表 * @return 转换后的实体列表 */ List<StudentBO> toDataProductList(List<StudentPO> dtoList); }
在 Service 中使用 Mapper
可以在业务逻辑中,用一行代码替代原来繁琐的转换过程。
package com.demo.springboot.controller; import com.demo.springboot.converter.StudentConverter; import com.demo.springboot.model.bo.StudentBO; import com.demo.springboot.model.po.StudentPO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; /** * 测试控制器 * @RestController 该注解是 @Controller 和 @ResponseBody 注解的组合注解,注解在类上就意味着该Controller的所有方法都默认加上了@ResponseBody。 * @ResponseBody 该注解将java对象转为json格式的数据,使用此注解之后不会再走视图处理器 , 可以把后台传到前端的数据自动转换为json */ @RestController public class HelloController { @Autowired private StudentConverter studentConverter; @RequestMapping("hello") public String hello() { //在Spring5.3之后,StringUtils.isEmpty()被标记为过时,官方推荐使用hasLength(String)或hasText(String)作为替代 boolean empty = StringUtils.hasText(""); System.out.println(StringUtils.isEmpty("")); System.out.println(StringUtils.hasText("")); System.out.println(StringUtils.hasLength("")); //将字符串按指定分隔符分割为字符串数组 String[] split = StringUtils.tokenizeToStringArray("ni shi ge cao bao", " "); for (String s:split) { System.out.println(s); } //使用 MapStruct 实现 Java 对象映射 List<StudentPO> poList = new ArrayList<>(); StudentPO student = new StudentPO(); student.setId(1); student.setName("张三"); poList.add(student); //将PO转换为BO List<StudentBO> boList = studentConverter.toDataProductList(poList); System.out.println("将PO转换为BO后集合大小:" + boList.size()); return "Hello Spring Boot!"; } }
注解介绍:@Mapper、@Mapping等
@Mapper注解
@Mapper注解用于标识一个接口为MapStruct映射接口。通过componentModel属性,可以指定生成的映射实现类的组件模型,如Spring的componentModel = "spring"。
@Mapper(uses = {Null.class}, componentModel = "spring")
public interface StudentConverter {
// 映射方法定义
}
@Mapping注解
@Mapping注解用于配置字段之间的映射关系。可以指定源属性、目标属性、以及转换表达式等。
基本数据类型转换
在Spring Boot项目中,经常会涉及到基本数据类型的转换,例如整数到字符串的转换、日期类型的处理等。MapStruct可以通过简单的注解配置来实现这些转换。
@Mapper(uses = {Null.class}, componentModel = "spring")
public interface StudentConverter {
/**
* 将单个 DTO 转换为实体。
* MapStruct 会自动映射所有同名、同类型的字段。
* 我们只需为有特殊转换需求的字段(如日期格式)添加规则。
* @param dto 源 DTO 对象
* @return 转换后的实体对象
*/
@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd'T'HH:mm:ss")
StudentBO toDataProduct(StudentPO dto);
/**
* 将 DTO 列表转换为实体列表。
* MapStruct 会自动复用上面的 toEntity 方法来处理列表中的每一个元素。
*
* @param dtoList 源 DTO 列表
* @return 转换后的实体列表
*/
List<StudentBO> toDataProductList(List<StudentPO> dtoList);
}
自定义映射方法
@Mapper(componentModel = "spring") public interface CustomMapper { @Mapping(target = "status", expression = "java(order.getStatus().getCode())") OrderDTO orderToOrderDTO(Order order); }
字符串与枚举的转换
在实际项目中,枚举类型的处理是常见的需求。MapStruct支持字符串与枚举类型之间的转换,通过qualifiedByName注解实现
@Mapper(componentModel = "spring") public interface OrderMapper { @Mapping(source = "status", target = "statusStr", qualifiedByName = "mapStatusEnumToString") OrderDTO orderToOrderDTO(Order order); @Named("mapStatusEnumToString") static String mapStatusEnumToString(OrderStatus status) { return status.toString(); } }
在上述例子中,我们通过qualifiedByName注解引用了一个自定义的静态方法mapStatusEnumToString,该方法负责将枚举类型转换为字符串。
嵌套对象的映射
当实体类中存在嵌套对象关系时,MapStruct可以轻松处理这种情况。
@Mapper(componentModel = "spring") public interface OrderMapper { @Mapping(source = "customer.name", target = "customerName") OrderDTO orderToOrderDTO(Order order); }
在上述例子中,我们通过customer.name指定了嵌套对象customer中的name属性到目标对象的映射。
集合类型的转换
处理集合类型是MapStruct的又一强项。例如,将订单列表转换为订单DTO列表。
@Mapper(componentModel = "spring") public interface OrderMapper { OrderDTO orderToOrderDTO(Order order); List<OrderDTO> ordersToOrderDTOs(List<Order> orders); }
MapStruct会递归处理集合中的元素,自动调用相应的映射方法完成转换。
映射规则自定义
在某些情况下,我们需要自定义映射规则,MapStruct允许通过自定义方法实现。
@Mapper(componentModel = "spring") public interface CustomMapper { @Mapping(target = "status", expression = "java(mapStatus(order.getStatus()))") OrderDTO orderToOrderDTO(Order order); default String mapStatus(OrderStatus status) { // 自定义映射逻辑 return status.toString(); } }
在上述例子中,我们通过expression属性调用了自定义方法mapStatus,实现了对订单状态的自定义映射。
条件映射与表达式
MapStruct支持条件映射和表达式的使用,可以根据某些条件决定是否进行映射,或者在映射过程中使用表达式进行计算。
@Mapper(componentModel = "spring") public interface OrderMapper { @Mapping(target = "status", expression = "java(mapStatus(order.getStatus()))") OrderDTO orderToOrderDTO(Order order); @Mapping(target = "discount", source = "totalAmount", condition = "java(order.getTotalAmount() > 1000)") OrderDTO orderToOrderDTOWithDiscount(Order order); default String mapStatus(OrderStatus status) { // 自定义映射逻辑 return status.toString(); } }
在上述例子中,通过condition属性,我们指定了映射discount属性的条件,只有在totalAmount大于1000时才进行映射。同时,通过expression属性,我们调用了自定义方法mapStatus实现了对状态的自定义映射。
高级配置
MapStruct提供了许多高级配置选项,可以在@Mapper注解中进行设置。例如,可以通过componentModel属性配置生成的映射实现类的组件模型,可以选择使用defaultComponentModel属性配置默认的组件模型。
@Mapper(componentModel = "spring", uses = {AnotherMapper.class}, injectionStrategy = InjectionStrategy.CONSTRUCTOR) public interface OrderMapper { OrderDTO orderToOrderDTO(Order order); }
在上述例子中,我们配置了使用Spring的组件模型,并且通过uses属性引入了另一个映射器AnotherMapper,通过injectionStrategy属性配置了依赖注入的策略为构造函数注入。
自定义转换器
在某些情况下,MapStruct的默认转换规则无法满足需求,这时可以使用自定义转换器。自定义转换器是一个带有@Mapper注解的类,其中包含了一些自定义的映射方法。
@Mapper(componentModel = "spring", uses = {CustomConverter.class}) public interface OrderMapper { OrderDTO orderToOrderDTO(Order order); }
在上述例子中,我们通过uses属性引入了自定义转换器CustomConverter,MapStruct将使用该转换器中的方法进行相应类型的转换。
性能优化
MapStruct性能对比
MapStruct通过在编译时生成映射代码,避免了运行时的性能开销。在大规模数据转换的场景下,MapStruct通常表现出色,并且通过合理的配置可以进一步提升性能。
缓存策略配置
MapStruct提供了缓存策略,可以通过unmappedTargetPolicy属性配置未映射字段的处理方式。例如,通过配置为IGNORE,可以忽略未映射的字段。
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface OrderMapper { OrderDTO orderToOrderDTO(Order order); }
在上述例子中,我们配置了unmappedTargetPolicy为IGNORE,即忽略未映射的字段。这样可以提高映射性能,同时避免了不必要的映射警告。
遇到的问题与解决方案
在实际应用中,可能会遇到一些问题,以下是一些可能的问题及解决方案:
- 循环引用问题: 当实体类之间存在循环引用时,可能导致栈溢出或无限递归。解决方案包括在映射接口上使用@Context注解,或者通过配置@Mapping注解的ignore属性来避免。
- 复杂的映射规则: 在涉及到复杂的映射规则时,可能需要使用自定义转换器或表达式,以满足特定业务需求。
- 性能问题: 对于大规模的数据转换,可能需要考虑性能优化。可以通过合理配置MapStruct的缓存策略、选择合适的组件模型等方式进行优化。

浙公网安备 33010602011771号