SuperAPI AI Directory

mapstruct-patterns

Coding & development

View repository

Updated 10-15 21:48

About this skill

MapStruct Best Practice Patterns

Source: MapStruct Reference Guide | GitHub: mapstruct/mapstruct

Capability Boundaries

✅ Strong Suits

  1. Three ways to define a Mapper — interface (standard) / abstract class (fields needed) / default method (custom logic)
  2. Spring injection strategies — CONSTRUCTOR (recommended, test-friendly) / SETTER (resolves circular dependencies)
  3. Nested mapping patterns — explicit sub-method encapsulation (recommended) > one-time dot notation configuration
  4. @Qualifier vs @Named — custom @Qualifier annotation (IDE refactoring safe) > @Named (string unsafe)
  5. Lombok integration — in annotationProcessorPaths, Lombok must come before MapStruct
  6. @MappingComposition — composed annotations are reusable, but error messages are immature, use with caution

❌ Out of Scope

  1. Runtime dynamic Bean mapping (Orika/Dozer) → MapStruct compile-time generation, not reflection

Core Patterns

Pattern 1: Choosing a Spring injection strategy

// ✅ 推荐:CONSTRUCTOR 注入(测试友好,不依赖Spring容器)
@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface UserMapper { UserVO toVO(User e); }

// ✅ 循环依赖:SETTER注入
@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.SETTER,
        uses = { OrderMapper.class })
public interface UserMapper { ... }

// ✅ 全局默认配置(避免每个接口重复)
@MapperConfig(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CentralConfig {}
@Mapper(config = CentralConfig.class) public interface UserMapper { ... }

Pattern 2: The correct way to do nested mapping

// ❌ 反模式:全写在顶层(重复配置,难以维护)
@Mapping(target = "address.street", source = "home.street")
@Mapping(target = "address.city", source = "home.city")
UserDTO toDTO(User e);

// ✅ 模式:显式子方法(单一配置点,可复用)
@Mapper
public interface UserMapper {
    UserDTO toDTO(User e);               // MapStruct 自动检测嵌套类型
    AddressDTO toAddressDTO(Address e);   // 子映射方法 — 唯一配置处
}

// ✅ dot notation + 子方法组合(局部覆盖)
@Mapping(target = "address.zip", constant = "00000")  // 常量覆盖
@Mapping(target = "address", source = "homeAddr")     // 其余走子方法
UserDTO toDTO(User e);

Pattern 3: @Qualifier > @Named

// ✅ 推荐:自定义 @Qualifier(IDE重构安全)
@Qualifier @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS)
public @interface ToUpperCase {}
@ToUpperCase default String toUpper(String s) { return s.toUpperCase(); }
@Mapping(target = "name", qualifiedBy = ToUpperCase.class)
UserDTO toDTO(User e);

// ⚠️ 可用但不推荐:@Named(字符串依赖,IDE不能自动重命名)
@Named("toUpper") default String toUpper(String s) { return s.toUpperCase(); }
@Mapping(target = "name", qualifiedByName = "toUpper")  // 字符串引用

Pattern 4: Spring configuration for circular dependencies

// 场景: UserMapper → OrderMapper → UserMapper
@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.SETTER,
        uses = { OrderMapper.class })  // ← SETTER是关键
public interface UserMapper {
    @Mapping(target = "orders", source = "orderList")
    UserDTO toDTO(User entity);
}
@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.SETTER,
        uses = { UserMapper.class })
public interface OrderMapper { ... }

Pattern 5: Abstract class Mapper

@Mapper
public abstract class UserMapper {
    @Autowired protected PasswordEncoder encoder;  // 可注入字段
    @Mapping(target = "encodedPwd", expression = "java(encoder.encode(dto.getPassword()))")
    public abstract UserVO toVO(UserDTO dto);
    protected String formatDate(LocalDateTime dt) { return dt.format(FORMATTER); } // 自定义方法
}

Gotchas

  1. Lombok's annotationProcessor must come before MapStruct — otherwise getters/setters cannot be found at compile time
  2. CONSTRUCTOR injection fails to compile with circular dependencies — switch to SETTER
  3. dot notation does not create sub-mapping methods — use explicit sub-methods when nested field logic is duplicated
  4. MapStruct generates at compile time, performance equals hand-written code — not reflection
  5. @Named string references are unsafe — IDE refactoring will not update them automatically, use a custom @Qualifier
  6. Do not explicitly use CollectionMappingStrategy.DEFAULT — only for internal distinction
  7. default methods in a Mapper interface take precedence over generated code — can be used for custom conversion logic

Data Privacy

This skill does not collect, store, or transmit any user data.