我们在写多层架构时,数据传输对象(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 : "";
}