Mapstruct:如何通过@MappingTarget 使用自定义映射器

Mapstruct: How to use custom mappers with @MappingTarget

我正在使用 Mapstruct,我需要使用 @MappingTarget 更新现有的 bean,但需要应用一些复杂的逻辑来设置目标中的正确字段。

假设我有一个看起来像这样的目标 bean。用户有一个帐户列表,其中一个帐户被标记为收藏。

UserDetails {
  String name;
  List<Account> accounts;
}

Account {
  String id;
  boolean favourite;
}

DTO class 包含他们最喜欢的帐户的帐户 ID。

UserDetialsDTO {
  String name;
  String favouriteAccountId;
  List<String> accountIds;
}

我需要使用一些复杂的逻辑来更新帐户列表中的正确 Account

UserDetails fromDto(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);

查找和更新正确的 Account 使其成为最爱的逻辑是这样的:

userDetails.accounts
           .stream()
           .forEach(acct -> acct.setFavourite(dto.favouriteAccountId.equals(acct.id))) ;

如何告诉 Mapstruct 在更新 @MapingTarget 时使用此自定义逻辑?

尝试:

    @Mapper 
    public interface MyMapper {

         @Mapping( target = "accounts", ignore = true ) 
         void fromDto(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);

         @AfterMapping
         default void handleAccounts(UserDetialsDTO dto, @MappingTarget UserDetails userDetails) {
             userDetails.accounts
               .stream()
               .forEach(acct -> acct.setFavourite(dto.favouriteAccountId.equals(acct.id))) ;
         }
    }

您可以使用 decorator 来同时实现:

  • Account 列表的初始化
  • 最喜欢的自定义逻辑

如:

@Mapper
@DecoratedWith(MyMapperDecorator.class)
public interface MyMapper {
    void fromDto(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);
}

public class MyMapperDecorator implements MyMapper{

    @Override
    public void fromDto(final UserDetialsDTO dto, final UserDetails userDetails) {
        if(dto == null){
            return;
        }

        dto.getAccountIds().forEach(a -> {
            final Account account = new Account();
            account.setId(a);
            account.setFavourite(a.equals(dto.getFavouriteAccountId()));
            userDetails.getAccounts().add(new Account());
        });

    }
}

为了避免重复,我建议使用 Set<Account> 而不是 List 并相应地实施 Account.equals()

另一种(稍微)更复杂的方法可能涉及定义一个 StringAccount 映射器使用它:

@Mapper(uses = AccountMapper.class)
@DecoratedWith(MyMapperDecorator.class)
interface MyMapper {

    MyMapper INSTANCE = Mappers.getMapper( MyMapper.class );

    @Mapping(target = "accounts", source="accountIds")
    abstract void update(UserDetialsDTO dto, @MappingTarget UserDetails userDetails);

}

public abstract class MyMapperDecorator implements MyMapper{

    private final MyMapper delegate;

    MyMapperDecorator(final MyMapper delegate){
        this.delegate = delegate;
    }

    @Override
    public void update(final UserDetialsDTO dto, final UserDetails userDetails) {
        if(dto == null){
            return;
        }

        delegate.update(dto, userDetails);

        userDetails.accounts
                .stream()
                .forEach(acct -> acct.setFavourite(dto.favouriteAccountId.equals(acct.id))) ;

    }
}

@Mapper
public interface AccountMapper {
    Account fromString(String id);

    void update(String id, @MappingTarget Account account);
}