MapStruct @IterableMapping 指南
1. 概述
MapStruct 是產生 Java Bean 映射器最強大的工具之一。有時我們需要對清單和其他可迭代物件進行更細緻的控制,尤其當每個元素都需要特定的格式或自訂映射邏輯時。這時, @IterableMapping就派上了用場。
在本教程中,我們將重點介紹如何使用@IterableMapping註解在 MapStruct 中處理集合映射。
2. 什麼是@IterableMapping ?
如其名稱所示, @IterableMapping通常用於配置 Java 集合如何從一種類型轉換為另一種類型。
當我們的映射需求超出簡單的類型轉換範圍時,MapStruct 註解可能就會達到其限制。此註解最常見的用途包括套用自訂格式、確保空值安全性以及解決映射歧義。
3. 實際案例
應用@IterableMapping註解非常簡單,它遵循與其他 `MapStruct` 註解相同的聲明式風格。要使用它,我們只需將註解直接添加到負責將一種可迭代類型轉換為另一種可迭代類型的方法上即可。
3.1 自訂元素映射
這裡,我們假設有一個字串日期列表,想要將其對應到LocalDate物件列表。為此,我們可以使用dateFormat屬性來確保轉換正確進行:
public interface DateMapper {
@IterableMapping(dateFormat = "yyyy-MM-dd")
List<LocalDate> stringsToLocalDates(List<String> dates);
}
現在,讓我們建立一個測試案例,以確保每個元素都按預期映射:
@Test
void givenStringDatewhenDateFormatIsUsed_thenMapToLocalDate() {
DateMapper mapper = Mappers.getMapper(DateMapper.class);
assertThat(mapper.stringsToLocalDates(List.of("2025-05-10", "2024-12-25")))
.containsExactly(LocalDate.of(2025, 5, 10), LocalDate.of(2024, 12, 25));
}
在本例中, dateFormat屬性是@IterableMapping邏輯的核心。它省去了手動編寫循環來處理日期格式化的麻煩。 MapStruct 會為我們產生這部分樣板程式碼。
現在,我們嘗試使用一個不符合所提供日期格式的日期字串,看看會發生什麼:
@Test
void givenStringDatewhenDateFormatIsNotRespected_thenThrowException() {
DateMapper mapper = Mappers.getMapper(DateMapper.class);
assertThatThrownBy(() -> mapper.stringsToLocalDates(List.of("2025/05/10")))
.isInstanceOf(DateTimeParseException.class);
}
正如我們所看到的,測試用例失敗並拋出DateTimeParseException異常。
3.2 選擇具體的合格方法
在許多實際場景中,基本的類型轉換並不總是足夠。例如,我們可能需要從映射中排除某個特定屬性,因為它包含敏感資料或太複雜而無法轉換。
在這種情況下,我們需要一個清晰的方法來告訴 MapStruct 應該對集合中的每個元素應用哪個自訂方法。為了解決這個問題, @IterableMapping提供了qualifiedByName屬性來指定特定的映射方法。
例如,我們考慮一個包含敏感資料password User類別:
public class User {
private String login;
private String password;
//standard full arguments constructor and getters
}
這裡的想法是將使用者密碼從映射邏輯中排除。因此,我們建立一個只包含login屬性的使用者 DTO:
public class UserDto {
private String login;
//standard full arguments constructor and getters
}
現在,我們需要利用一種專門的映射方法來完全繞過密碼屬性:
public interface UserMapper {
@IterableMapping(qualifiedByName = "mapLoginOnly")
List<UserDto> toDto(List<User> users);
@Named("mapLoginOnly")
default UserDto mapLoginOnly(User user) {
return user != null ? new UserDto(user.getLogin()) : null;
}
}
透過使用qualifiedByName ,我們指示 MapStruct 使用mapLoginOnly()方法來對應所提供清單中的每個元素:
@Test
void givenUserWithPasswordwhenExcludePassword_thenConvertLoginOnly() {
UserMapper mapper = Mappers.getMapper(UserMapper.class);
List<UserDto> result = mapper.toDto(List.of(new User("admin", "@admin@2026")));
assertThat(result.get(0)).usingRecursiveComparison().isEqualTo(new UserDto("admin"));
}
測試案例證明qualifiedByName實際上透過mapLoginOnly ()方法路由每個使用者。
4. 處理空集合
預設情況下,如果給定的集合為null ,MapStruct 將傳回null 。但是,我們可以使用nullValueMappingStrategy特性來覆寫此行為。
通常,我們可以使用NullValueMappingStrategy.RETURN_DEFAULT傳回一個空集合而不是null 。這對於防止下游邏輯中出現NullPointerException異常尤其有用。
例如,讓我們更新toDto()方法上使用的@IterableMapping註解:
@IterableMapping(qualifiedByName = "mapLoginOnly",
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
現在,我們嘗試傳遞一個null值:
@Test
void whenListIsNull_thenReturnEmptyCollection() {
UserMapper mapper = Mappers.getMapper(UserMapper.class);
assertThat(mapper.toDto(null)).isEmpty();
}
正如預期的那樣,新的測試案例運行成功,證實我們的方法返回的是一個空集合而不是 null。
5. 結論
本文闡述了@IterableMapping註解如何提供對集合映射的精細控制。我們舉例說明了該註解如何提供自訂格式、方法選擇和空值安全等必要的介面。
完整的源代碼可以在 GitHub 上找到。