在我们深入探讨之前,先快速回顾一下MapStruct是什么。它是一个代码生成器,在编译时创建类型安全的bean映射器。没有运行时开销,没有反射魔法——只有纯粹、高效的Java代码。以下是一个基本映射器的示例:


@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
    UserDTO mapToDTO(User user);
}

简单吧?但我们才刚刚开始!

你可能不知道的10个MapStruct技巧

1. 带上下文的映射:秘密武器

是否曾经需要向映射器传递额外信息?试试@Context注解:


@Mapper
public interface UserMapper {
    @Mapping(target = "role", source = "user")
    UserDTO mapToDTO(User user, @Context SecurityContext securityContext);
    
    default String mapRole(User user, @Context SecurityContext securityContext) {
        return securityContext.isAdmin() ? "ADMIN" : user.getRole();
    }
}

现在你可以在映射逻辑中注入运行时上下文。很酷吧?

2. 自定义方法映射:当标准方法不够用时

有时候,你需要亲自编写自定义映射逻辑:


@Mapper
public interface DateMapper {
    @Mapping(target = "formattedDate", source = "date", qualifiedByName = "formatDate")
    DateDTO map(DateEntity date);
    
    @Named("formatDate")
    default String formatDate(LocalDate date) {
        return date != null ? date.format(DateTimeFormatter.ISO_LOCAL_DATE) : null;
    }
}

小贴士:使用@Named来组织和重用不同映射器中的自定义方法。

3. 集合映射:因为一个从来不够

MapStruct处理集合如同高手:


@Mapper
public interface ProductMapper {
    List mapToDTOList(List products);
    
    Set mapToProductNames(List products);
}

它会自动将元素映射应用于集合中的每个项目。神奇吧!

4. 嵌套对象映射:深入探讨

处理复杂对象图?MapStruct为你保驾护航:


@Mapper
public interface OrderMapper {
    @Mapping(target = "customerName", source = "customer.name")
    @Mapping(target = "orderItems", source = "items")
    OrderDTO mapToDTO(Order order);
    
    @Mapping(target = "productName", source = "product.name")
    OrderItemDTO mapToItemDTO(OrderItem item);
}

它无缝处理嵌套对象,免去手动映射的麻烦。

5. 条件映射:决策的艺术

有时,你需要即时做出映射决策:


@Mapper
public interface UserMapper {
    @Mapping(target = "status", expression = "java(mapStatus(user))")
    UserDTO mapToDTO(User user);
    
    default String mapStatus(User user) {
        return user.getLastLoginDate() != null && 
               user.getLastLoginDate().isAfter(LocalDate.now().minusDays(30)) 
               ? "ACTIVE" : "INACTIVE";
    }
}

使用表达式将复杂逻辑注入到映射中。

6. 忽略字段:选择性映射的艺术

有时候,少即是多。忽略不需要的字段:


@Mapper
public interface UserMapper {
    @Mapping(target = "password", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    UserDTO mapToDTO(User user);
}

非常适合处理敏感数据或不属于DTO的字段。

7. 反向映射:双向进行

需要来回映射?MapStruct为你提供支持:


@Mapper
public interface UserMapper {
    @Mapping(target = "id", ignore = true)
    User mapToEntity(UserDTO dto);
    
    UserDTO mapToDTO(User user);
}

只需定义两个方法,MapStruct会为你处理双向映射。

8. 默认值:填补空白

有时,你需要为缺失的数据提供默认值:


@Mapper
public interface ProductMapper {
    @Mapping(target = "inStock", constant = "true")
    @Mapping(target = "category", defaultValue = "UNCATEGORIZED")
    ProductDTO mapToDTO(Product product);
}

使用constant为硬编码值,使用defaultValue为源为空时的备用值。

9. 映射前后:最后的润色

需要在映射前后执行一些操作?试试@BeforeMapping和@AfterMapping:


@Mapper
public abstract class OrderMapper {
    @Mapping(target = "total", ignore = true)
    public abstract OrderDTO mapToDTO(Order order);
    
    @AfterMapping
    protected void calculateTotal(Order order, @MappingTarget OrderDTO orderDTO) {
        orderDTO.setTotal(order.getItems().stream()
                               .mapToDouble(item -> item.getPrice() * item.getQuantity())
                               .sum());
    }
}

非常适合复杂计算或最后的调整。

10. 自定义MapStruct注解:抽象的力量

创建自己的注解以封装常见的映射模式:


@Retention(RetentionPolicy.CLASS)
@Mapping(target = "id", ignore = true)
@Mapping(target = "createdAt", expression = "java(java.time.LocalDateTime.now())")
public @interface ToEntity { }

@Mapper
public interface UserMapper {
    @ToEntity
    User mapToEntity(UserDTO dto);
}

这种方法有助于保持映射器的整洁,并促进代码库中的重用。

总结:MapStruct的魔力释放

以上就是10个MapStruct技巧,它们将提升你的Quarkus映射水平。从上下文感知的映射到自定义注解,MapStruct充满了可以简化复杂对象转换的功能。

记住,掌握MapStruct的关键在于实验。不要害怕深入文档,尝试这些技术的不同组合。你的未来自我(以及代码审查员)会感谢你编写的干净、高效且可维护的映射代码。

所以,下次当你想手动设置时,退一步问问自己:“MapStruct会怎么做?”很可能,它有一个技巧可以让你的生活轻松很多。

祝映射愉快,Quarkus忍者们!🚀

“映射的艺术不在于创建新对象,而在于揭示现有对象之间的关系。” - 匿名开发者(可能)

附言:如果你觉得MapStruct有用,别忘了在GitHub上为MapStruct仓库加星。开源项目依赖社区支持!