java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java MapStruct使用

Java MapStruct使用配置实战指南

作者:猩火燎猿

MapStruct是一个高效、类型安全的JavaBean映射工具,通过编译时注解处理器生成映射代码,它支持自动映射、自定义转换、嵌套对象和集合的映射,并且可以与Spring集成,文章介绍了MapStruct的使用方法,以及与BeanUtils、Dozer、ModelMapper等框架的比较,感兴趣的朋友一起看看吧

一、MapStruct 简介

MapStruct 是一个编译期注解处理器,它会在编译时自动生成类型安全、无反射的 Java Bean 映射代码。
核心优势:

二、MapStruct 工作原理

  1. 编写 Mapper 接口,使用 @Mapper 注解。
  2. 编译时,MapStruct 的注解处理器根据接口定义自动生成实现类(通常在 target/generated-sources/annotations 下)。
  3. 运行时,调用自动生成的实现类进行对象转换。

三、快速入门示例

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);
}

五、常见问题

六、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> { }

十四. 常见坑及调试方法

十五、其他扩展

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。
解决:

(5)自定义方法未被调用

原因: 没有用 @Named 和 qualifiedByName 关联。
解决: 方法加 @Named("xxx")@Mapping 里加 qualifiedByName = "xxx"

6. 进阶建议

参考示例

@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使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文