MapStruct之映射集合

1,279 阅读4分钟

映射集合

集合类型(List、Set等)的映射与bean类型的映射相同,即通过在映射器接口中定义具有所需源和目标类型的映射方法来完成。MapStruct支持Java Collection Framework中的各种可迭代类型。
生成的代码将包含一个循环,遍历源集合,转换每个元素并将其放入目标集合中。如果在给定的映射器或它使用的映射器中找到集合元素类型的映射方法,则调用该方法执行元素转换。或者,如果存在源和目标元素类型的隐式转换,则将调用此转换以下是一个示例:
源对象:

@Getter
@Setter
public class FamilyDTO2 {
    private List<User> users;

    private List<Address> addresses;
}

目标对象:

@Getter
@Setter
public class FamilyDetail2 {
    List<UserDetailDTO> users;
    List<Address> addresses;
}

带有集合映射方法的映射器

@Mapper(componentModel = "spring")
public interface FamilyConverter {
    /**
     * 存在隐式转换
     * @param integers
     * @return
     */
    Set<String> integerSetToStringSet(Set<Integer> integers);

    FamilyDetail2 convert(FamilyDTO2 dto);

    List<UserDetailDTO> convertUserList(List<User> user);
}

生成的integerSetToStringSet实现为每个元素执行从Integer到String的隐式转换,而生成的convertUserList方法为convert方法中调用,如下所示:

@Component
public class FamilyConverterImpl implements FamilyConverter {

    @Override
    public Set<String> integerSetToStringSet(Set<Integer> integers) {
        if ( integers == null ) {
            return null;
        }

        Set<String> set = new HashSet<String>( Math.max( (int) ( integers.size() / .75f ) + 1, 16 ) );
        for ( Integer integer : integers ) {
            set.add( String.valueOf( integer ) );
        }

        return set;
    }

    @Override
    public FamilyDetail2 convert(FamilyDTO2 dto) {
        if ( dto == null ) {
            return null;
        }

        FamilyDetail2 familyDetail2 = new FamilyDetail2();

        familyDetail2.setUsers( convertUserList( dto.getUsers() ) );
        List<Address> list1 = dto.getAddresses();
        if ( list1 != null ) {
            familyDetail2.setAddresses( new ArrayList<Address>( list1 ) );
        }

        return familyDetail2;
    }

    @Override
    public List<UserDetailDTO> convertUserList(List<User> user) {
        if ( user == null ) {
            return null;
        }

        List<UserDetailDTO> list = new ArrayList<UserDetailDTO>( user.size() );
        for ( User user1 : user ) {
            list.add( userToUserDetailDTO( user1 ) );
        }

        return list;
    }

    protected UserDetailDTO userToUserDetailDTO(User user) {
        if ( user == null ) {
            return null;
        }

        UserDetailDTO userDetailDTO = new UserDetailDTO();

        userDetailDTO.setId( user.getId() );
        userDetailDTO.setName( user.getName() );
        userDetailDTO.setAge( user.getAge() );
        userDetailDTO.setNickName( user.getNickName() );

        return userDetailDTO;
    }
}

映射Map

MapStructy也支持映射Map,通过注解@MapMapping可以指定键或者值映射类型转换,如下例子:

public interface SourceTargetMapper {

    @MapMapping(valueDateFormat = "dd.MM.yyyy")
    Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}

与前面集合映射类似,生成的代码将遍历源映射,转换每个值和键(通过隐式转换或调用另一种映射方法),并将它们放入目标映射。
生成的代码:

@Override
public Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source) {
    if ( source == null ) {
        return null;
    }

    Map<String, String> map = new HashMap<String, String>( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) );

    for ( java.util.Map.Entry<Long, Date> entry : source.entrySet() ) {
        String key = new DecimalFormat( "" ).format( entry.getKey() );
        String value = new SimpleDateFormat( "dd.MM.yyyy" ).format( entry.getValue() );
        map.put( key, value );
    }

    return map;
}

集合映射策略

MapStruct有一个CollectionMappingStrategy,可选的值为:ACCESSOR_ONLY(默认策略)、SETTER_PREFERRED、ADDER_PREFERRED和TARGET_IMMUTABLE。
CollectionMappingStrategy 枚举类型定义了以下四种策略:

选项只有set方法修改集合只有add方法修改集合set、add方法都有修改集合set、add方法都没有已经存在的集合对象
ACCESSOR_ONLY直接set集合先get获取集合再addAll直接set集合先get获取集合再addAll先get获取集合再addAll
SETTER_PREFERRED直接set集合add集合一项直接set集合先get获取集合再addAll先get获取集合再addAll
ADDER_PREFERRED直接set集合add集合一项add集合一项先get获取集合再addAll先get获取集合再addAll
TARGET_IMMUTABLE直接set集合异常直接set集合异常直接set集合

不同的集合映射策略适用于不同的场景,可以根据具体需求来选择合适的策略。例如,我们项目开发中对于目标对象更新操作时,需求是将前端传来源对象中带有集合的属性直接set到目标对象中(源对象集合属性没有数据时统一以Collections.emptyList() 返回),MapStruct默认策略是ACCESSOR_ONLY 针对以存在的集合对象以下面代码的方式处理的

if ( dto2.getAddresses() != null ) {
    List<Address> list1 = dto.getAddresses();
    if ( list1 != null ) {
        dto2.getAddresses().clear();
        dto2.getAddresses().addAll( list1 );
    }
    else {
        dto2.setAddresses( null );
    }
}

即由于目标对象类型是Collections.emptyList() 不等于null,清空目标集合对象将源对象list1以addAll形式添加到目标对象中。会报UnsupportedOperationException 这时候就需要直接键源对象的集合对象直接set到目标对象中,就需要将策略设置成TARGET_IMMUTABLE 即可。下面是TARGET_IMMUTABLE策略生成的代码:

if ( list1 != null ) {
    dto2.setAddresses( new ArrayList<Address>( list1 ) );
}

用于集合映射的实现类型

当一个可迭代对象或映射对象的映射方法声明一个接口类型作为返回类型时,在生成的代码中会实例化其实现类型之一。下表显示了支持的接口类型及其在生成的代码中被实例化的相应实现类型:

接口类型实现类型
IterableArrayList
CollectionArrayList
ListArrayList
SetHashSet
SortedSetTreeSet
NavigableSetTreeSet
MapHashMap
SortedMapTreeMap
NavigableMapTreeMap
ConcurrentMapConcurrentHashMap
ConcurrentNavigableMapConcurrentSkipListMap