Java MapStruct使用配置实战指南
作者:猩火燎猿
一、MapStruct 简介
MapStruct 是一个编译期注解处理器,它会在编译时自动生成类型安全、无反射的 Java Bean 映射代码。
核心优势:
- 高性能:无运行时反射,生成代码直接调用 getter/setter。
- 类型安全:编译期检查,映射出错时编译直接报错。
- 易于集成:只需引入依赖,配合注解即可。
二、MapStruct 工作原理
- 编写 Mapper 接口,使用
@Mapper注解。 - 编译时,MapStruct 的注解处理器根据接口定义自动生成实现类(通常在
target/generated-sources/annotations下)。 - 运行时,调用自动生成的实现类进行对象转换。
三、快速入门示例
1. 添加依赖
Maven:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>2. 定义源对象和目标对象
// Entity
public class UserEntity {
private Long id;
private String name;
private String email;
// getter/setter
}
// DTO
public class UserDTO {
private Long id;
private String name;
// getter/setter
}3. 编写 Mapper 接口
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO entityToDto(UserEntity entity);
UserEntity dtoToEntity(UserDTO dto);
}4. 使用 Mapper
UserEntity entity = new UserEntity();
entity.setId(1L);
entity.setName("Tom");
entity.setEmail("tom@example.com");
UserDTO dto = UserMapper.INSTANCE.entityToDto(entity);
// dto.id = 1, dto.name = "Tom"四、进阶用法
1. 字段名不一致
public class UserEntity {
private String userName;
//...
}
public class UserDTO {
private String name;
//...
}
@Mapper
public interface UserMapper {
@Mapping(source = "userName", target = "name")
UserDTO entityToDto(UserEntity entity);
}2. 嵌套对象映射
public class AddressEntity { ... }
public class AddressDTO { ... }
public class UserEntity {
private AddressEntity address;
}
public class UserDTO {
private AddressDTO address;
}
@Mapper
public interface UserMapper {
UserDTO entityToDto(UserEntity entity);
}MapStruct 会自动递归调用同名映射方法。
3. 集合映射
List<UserDTO> entityListToDtoList(List<UserEntity> entities);
4. 自定义转换
@Mapper
public interface UserMapper {
@Mapping(target = "createTime", expression = "java(new java.util.Date())")
UserDTO entityToDto(UserEntity entity);
}五、常见问题
- 生成代码找不到?
- 检查 IDE 的 annotation processing 是否开启,代码在
target/generated-sources/annotations下。
- 检查 IDE 的 annotation processing 是否开启,代码在
- 自定义方法怎么用?
- 可以在 Mapper 里定义 default 方法,或用
@Named结合@Mapping的qualifiedByName。
- 可以在 Mapper 里定义 default 方法,或用
- 与 Spring 集成?
- 用
@Mapper(componentModel = "spring"),Mapper 会注册为 Spring Bean,可自动注入。
- 用
六、MapStruct 与 BeanUtils/Dozer/ModelMapper 比较
| 框架 | 性能 | 类型安全 | 反射 | 编译期检查 | 代码生成 |
|---|---|---|---|---|---|
| MapStruct | 很高 | 是 | 否 | 是 | 是 |
| BeanUtils | 一般 | 否 | 是 | 否 | 否 |
| Dozer | 较低 | 否 | 是 | 否 | 否 |
| ModelMapper | 较低 | 否 | 是 | 否 | 否 |
七. Spring 集成与依赖注入
让 Mapper 变成 Spring Bean,只需加 @Mapper(componentModel = "spring"):
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDTO entityToDto(UserEntity entity);
}然后就可以在 Spring 中自动注入:
@Autowired private UserMapper userMapper;
如果 Mapper 之间有依赖,可以直接注入其他 Mapper:
@Mapper(componentModel = "spring", uses = {AddressMapper.class})
public interface UserMapper { ... }八. 多级嵌套对象映射
比如 DTO 和 Entity 里都包含 Address 对象:
public class UserEntity {
private AddressEntity address;
}
public class UserDTO {
private AddressDTO address;
}
@Mapper
public interface AddressMapper {
AddressDTO entityToDto(AddressEntity entity);
}
@Mapper(uses = {AddressMapper.class})
public interface UserMapper {
UserDTO entityToDto(UserEntity entity);
}MapStruct 会自动调用 AddressMapper 的方法。
九. 枚举类型映射
如果枚举名一致,MapStruct 会自动映射;如果不一致,可以手动指定:
public enum StatusEnum { ENABLED, DISABLED }
public enum StatusDTO { ON, OFF }
@Mapper
public interface StatusMapper {
@Mapping(source = "ENABLED", target = "ON")
@Mapping(source = "DISABLED", target = "OFF")
StatusDTO toDto(StatusEnum status);
}十. 自定义类型转换(QualifiedByName、@Named)
比如把 String 转成 Date:
@Mapper
public interface UserMapper {
@Mapping(source = "dateStr", target = "date", qualifiedByName = "stringToDate")
UserDTO entityToDto(UserEntity entity);
@Named("stringToDate")
default Date stringToDate(String dateStr) {
// 你自己的转换逻辑
return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
}
}十一. 更新已有对象(@MappingTarget)
有时需要把 DTO 的值“更新”到已存在的 Entity:
@Mapper
public interface UserMapper {
void updateEntityFromDto(UserDTO dto, @MappingTarget UserEntity entity);
}这样不会新建对象,而是直接修改传入的 entity。
十二. 表达式与常量映射
如果目标字段是常量或需要表达式:
@Mapper
public interface UserMapper {
@Mapping(target = "status", expression = "java(entity.isActive() ? \"ACTIVE\" : \"INACTIVE\")")
@Mapping(target = "role", constant = "USER")
UserDTO entityToDto(UserEntity entity);
}十三. 映射继承与多态
可以通过接口继承复用映射:
@Mapper
public interface BaseMapper<E, D> {
D toDto(E entity);
E toEntity(D dto);
}
@Mapper
public interface UserMapper extends BaseMapper<UserEntity, UserDTO> { }十四. 常见坑及调试方法
- IDE未生成 Mapper 实现类?
- 检查 annotation processing 是否打开,Maven/IDEA/VSCode 都要设置。
- 字段名不一致未映射?
- 用
@Mapping(source, target)明确指定。
- 用
- 集合、嵌套类型未自动转换?
- 检查是否正确配置
uses,相关 Mapper 是否存在。
- 检查是否正确配置
- 调试生成代码?
- 直接到
target/generated-sources/annotations查看生成的实现类,理解 MapStruct 的处理逻辑。
- 直接到
- 复杂类型转换报错?
- 用
@Named,qualifiedByName明确指定转换方法。
- 用
十五、其他扩展
1. LocalDate/LocalDateTime 映射
场景:DTO 里是 String,Entity 里是 LocalDate。
public class UserEntity {
private LocalDate birthday;
}
public class UserDTO {
private String birthday; // "2024-07-11"
}Mapper 写法:
@Mapper
public interface UserMapper {
@Mapping(source = "birthday", target = "birthday", qualifiedByName = "stringToLocalDate")
UserEntity dtoToEntity(UserDTO dto);
@Named("stringToLocalDate")
default LocalDate stringToLocalDate(String dateStr) {
return LocalDate.parse(dateStr);
}
}反向转换:
@Mapping(source = "birthday", target = "birthday", qualifiedByName = "localDateToString")
@Named("localDateToString")
default String localDateToString(LocalDate date) {
return date != null ? date.toString() : null;
}2. BigDecimal 映射
场景:DTO 是 String 或 Double,Entity 是 BigDecimal。
public class ProductEntity {
private BigDecimal price;
}
public class ProductDTO {
private String price;
}Mapper 写法:
@Mapper
public interface ProductMapper {
@Mapping(source = "price", target = "price", qualifiedByName = "stringToBigDecimal")
ProductEntity dtoToEntity(ProductDTO dto);
@Named("stringToBigDecimal")
default BigDecimal stringToBigDecimal(String price) {
return price != null ? new BigDecimal(price) : null;
}
}反向同理,写个 bigDecimalToString 方法。
3. 枚举类型映射
场景:DTO 和 Entity 枚举名不同/枚举类型不同。
public enum StatusEntity { ENABLED, DISABLED }
public enum StatusDTO { ON, OFF }Mapper 写法:
@Mapper
public interface StatusMapper {
@Mapping(source = "ENABLED", target = "ON")
@Mapping(source = "DISABLED", target = "OFF")
StatusDTO toDto(StatusEntity status);
}或者用自定义方法:
@Named("statusToDto")
default StatusDTO statusToDto(StatusEntity status) {
switch (status) {
case ENABLED: return StatusDTO.ON;
case DISABLED: return StatusDTO.OFF;
default: return null;
}
}4. 嵌套集合映射
场景:DTO 和 Entity 都有嵌套集合,如 List、Set。
public class OrderEntity {
private List<ItemEntity> items;
}
public class OrderDTO {
private List<ItemDTO> items;
}Mapper 写法:
@Mapper
public interface ItemMapper {
ItemDTO entityToDto(ItemEntity entity);
}
@Mapper(uses = ItemMapper.class)
public interface OrderMapper {
OrderDTO entityToDto(OrderEntity entity);
List<OrderDTO> entityListToDtoList(List<OrderEntity> entities);
}MapStruct 会自动将集合中的每个元素递归映射。
5. 常见 MapStruct 报错及解决
(1)找不到映射方法
报错内容:
No property named 'xxx' exists in source parameter(s).
原因: DTO/Entity 字段名不一致或拼写错误。
解决: 用 @Mapping(source = "xxx", target = "yyy") 显式指定。
(2)类型不兼容
报错内容:
Can't map property "java.lang.String price" to "java.math.BigDecimal price".
原因: MapStruct 不知道怎么转换 String 到 BigDecimal。
解决: 写自定义转换方法,并用 qualifiedByName 指定。
(3)嵌套集合或对象未自动映射
报错内容:
No implementation for method entityToDto(ItemEntity entity) found.
原因: ItemMapper 没有被 uses 引用,或没有实现方法。
解决: 在主 Mapper 上加 uses = {ItemMapper.class},并实现相关方法。
(4)编译未生成实现类
原因: IDEA/Maven 未开启 annotation processing。
解决:
- IDEA: Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enable。
- Maven:
mvn clean compile,确保target/generated-sources/annotations下有实现类。
(5)自定义方法未被调用
原因: 没有用 @Named 和 qualifiedByName 关联。
解决: 方法加 @Named("xxx"),@Mapping 里加 qualifiedByName = "xxx"。
6. 进阶建议
- 对于复杂类型转换,建议都用
@Named标记方法,方便复用和维护。 - 对于枚举、日期、金额等类型,推荐写专门的 Mapper 或 Converter 类。
- 多层嵌套/集合映射时,合理拆分 Mapper,避免主 Mapper 过于庞大。
参考示例
@Mapper
public interface UserMapper {
@Mapping(source = "birthday", target = "birthday", qualifiedByName = "stringToLocalDate")
@Mapping(source = "balance", target = "balance", qualifiedByName = "stringToBigDecimal")
UserEntity dtoToEntity(UserDTO dto);
@Named("stringToLocalDate")
default LocalDate stringToLocalDate(String dateStr) {
return dateStr != null ? LocalDate.parse(dateStr) : null;
}
@Named("stringToBigDecimal")
default BigDecimal stringToBigDecimal(String val) {
return val != null ? new BigDecimal(val) : null;
}
}到此这篇关于Java MapStruct使用配置实战指南的文章就介绍到这了,更多相关Java MapStruct使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
