我们在写多层架构时,数据传输对象(DTO)、实体类和其他业务对象之间的转换是不可避免的。手动编写这些映射逻辑不仅耗时而且容易出错。为了提高开发效率和代码质量,我们建议引入 MapStruct 作为我们的对象映射工具。
类型安全: 编译器会在编译时检查映射逻辑,确保源对象和目标对象的字段匹配。
错误报告: 如果映射出现问题,编译器会立即报错,便于调试和修复。
无反射: 生成的映射代码不依赖反射,执行速度快。
零运行时开销: 不需要额外的库或运行时组件。
注解驱动: 使用简洁的注解来定义映射规则,易于理解和维护。
默认行为: 提供合理的默认行为,减少样板代码。
嵌套对象: 自动处理嵌套对象的映射。
集合映射: 支持列表、数组等多种集合类型的映射。
自定义方法: 可以通过自定义方法实现复杂的映射逻辑。
枚举映射: 支持枚举类型的映射。
<dependencies> <!-- Spring Boot Starter Data JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- MapStruct --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.3.Final</version> </dependency> <!-- MapStruct Processor --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.3.Final</version> <scope>provided</scope> </dependency> <!-- Lombok (Optional, for reducing boilerplate code) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.3.Final</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity publicclass Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String street; private String city; // Getters and Setters (or use Lombok) }
import javax.persistence.*; import java.util.List; @Entity publicclass User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Integer age; private Boolean isActive; @OneToOne(cascade = CascadeType.ALL) private Address address; @OneToMany(cascade = CascadeType.ALL) private List<Address> addresses; // Getters and Setters (or use Lombok) }
public class AddressDto { private Long id; private String street; private String city; // Getters and Setters (or use Lombok) }
public enum Status { ACTIVE, INACTIVE }
import java.util.List; publicclass UserDto { private Long id; private String name; private Integer age; private Status status; private AddressDto address; private List<AddressDto> addresses; // Getters and Setters (or use Lombok) }
创建两个 Mapper 接口来分别处理
Address
到AddressDto
的映射和User
到UserDto
的映射。
import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapper(componentModel = "spring") public interface AddressMapper { AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class); AddressDto addressToAddressDto(Address address); Address addressDtoToAddress(AddressDto addressDto); }
import org.mapstruct.*; import org.mapstruct.factory.Mappers; import java.util.List; @Mapper(componentModel = "spring", uses = {AddressMapper.class}) public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @Mapping(source = "address", target = "address") @Mapping(source = "addresses", target = "addresses") @Mapping(target = "status", source = "isActive", qualifiedByName = "activeToStatus") @Mapping(target = "age", defaultValue = "18") // Default value if age is null UserDto userToUserDto(User user); @Mapping(source = "address", target = "address") @Mapping(source = "addresses", target = "addresses") @Mapping(target = "isActive", source = "status", qualifiedByName = "statusToActive") User userDtoToUser(UserDto userDto); @Named("activeToStatus") default Status activeToStatus(Boolean isActive) { return isActive != null && isActive ? Status.ACTIVE : Status.INACTIVE; } @Named("statusToActive") default Boolean statusToActive(Status status) { return status == Status.ACTIVE; } }
默认值 (defaultValue
):
在 UserMapper
中,@Mapping(target = "age", defaultValue = "18")
指定了如果 User
对象中的 age
属性为 null
,则默认值为 18
。
条件映射 (qualifiedByName
):
使用 @Named
注解定义了两个方法 activeToStatus
和 statusToActive
,用于在 User
和 UserDto
之间进行状态转换。
这些方法通过 @Mapping(target = ..., source = ..., qualifiedByName = ...)
被引用。
自定义方法:
自定义方法 activeToStatus
和 statusToActive
直接在 UserMapper
接口中实现,提供了灵活的数据转换逻辑。
枚举映射:
通过自定义方法将布尔类型的 isActive
字段映射到枚举类型的 Status
字段,并反之。
MapStruct 默认不支持循环引用,但可以通过自定义方法或使用 @AfterMapping
注解来处理循环引用的情况。
使用 @Mapping
注解中的 source
和 target
属性来指定不同的属性名称:
@Mapping(source = "entityPropertyName", target = "dtoPropertyName")
使用 @Mapping
注解中的 dateFormat
属性:
@Mapping(source = "dateField", dateFormat = "yyyy-MM-dd")
使用 @IterableMapping
注解结合 qualifiedByName
或 qualifiedBy
来过滤集合:
@IterableMapping(qualifiedByName = "filterNulls") List<String> filterStrings(List<String> strings); @Named("filterNulls") default String filterNulls(String string) { return string != null ? string : ""; }