Mapstruct 中的字串映射
1. 概述
在使用 Java 應用程式時,我們可能需要將複雜的資料物件轉換為更簡單的表示形式。例如,為了方便顯示、日誌記錄或 API 回應,我們通常會將枚舉、數字或巢狀物件等資料類型轉換為對應的String 。在這種情況下,我們可以使用MapStruct 。
在本教程中,我們將把不同的資料類型對應到String 。
2. 項目設定
為了示範如何將MapStruct對應到String ,我們可以建立一個簡單的 Maven 專案mapstructstringmapping 。
在pom.xml檔中,我們導航到mapstructstringmapping目錄並進行更新:
<dependencies>
<!-- MapStruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.14.2</version>
<scope>test</scope>
</dependency>
</dependencies>
上面,我們加入了用於編譯時映射的[mapstruct](https://mvnrepository.com/artifact/org.mapstruct/mapstruct) ,以及用於測試的JUnit 5 。
3. 簡單的字串映射
在處理演示或 API 回應時,我們可能需要將數值或布林值轉換為字串。一個很好的例子是,資料庫實體將使用者的年齡儲存為int ,但 API 層需要將其作為String公開。
讓我們來看一個簡單的例子,展示如何使用MapStruct將int欄位對應到String 。首先,我們定義Person類別:
public class Person {
private String name;
private int age;
}
接下來,我們建立第二個類別 PersonDTO:
public class PersonDTO {
private String name;
private String age;
}
在上述範例中,這些類別具有標準的 getter 和 setter, MapStruct將使用這些 getter 和 setter 進行對應。
現在,讓我們定義映射器介面PersonMapper.java :
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
PersonDTO toDTO(Person person);
}
在編譯時, MapStruct會建立PersonMapper實作。在這種情況下,當呼叫toDTO()方法時, MapStruct自動將來源物件中 age 的int值變更為目標物件中的String 。
讓我們建立PersonMapperUnitTest.java單元測試,並確保映射功能能如預期運作:
public class PersonMapperUnitTest {
@Test
void givenPerson_whenMapsToPersonDTO_thenFieldsAreCorrect() {
Person person = new Person();
person.setName("Alice");
person.setAge(30);
PersonDTO dto = PersonMapper.INSTANCE.toDTO(person);
assertEquals("Alice", dto.getName());
assertEquals("30", dto.getAge());
}
}
測試確認年齡的int數值已轉換為String表示形式,且姓名欄位已成功對應。
4. 枚舉到字串的轉換
在 Java 中,枚舉類型廣泛用於定義固定的變數集,例如使用者角色、訂單狀態和系統狀態。然而,當資料在使用者介面中顯示或透過 API 提供存取時,這些enum值通常會被渲染成字串。
4.1. 定義枚舉類別和域類
我們先來定義一個enum ,用來表示使用者的狀態:
public enum Status {
ACTIVE,
INACTIVE,
PENDING
}
接下來,我們建立一個使用此enum的領域物件:
public class User {
private String username;
private Status status;
}
最後,我們建立一個DTO,其中enum值表示為String :
public class UserDTO {
private String username;
private String status;
}
現在,User 類別使用enum作為狀態字段,而UserDTO期望狀態字段的值是String 。
4.2. 定義映射器
讓我們定義一個映射器接口,將User轉換為UserDTO :
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO toDto(User user);
}
在這裡, MapStruct會自動偵測到來源欄位是enum ,目標欄位是String 。我們不需要定義任何自訂映射邏輯。它會在映射過程中呼叫枚舉的name()方法。
在大多數實際 API 中,這種預設行為就足夠了,但如果enum名稱與客戶端期望的值不匹配,則需要注意這一點。
4.3 驗證映射關係
讓我們編寫一個單元測試,以驗證enum-to-string轉換是否按預期工作:
public class UserMapperUnitTest {
@Test
void shouldMapEnumToString() {
User user = new User();
user.setUsername("Kevin");
user.setStatus(Status.ACTIVE);
UserDTO dto = UserMapper.INSTANCE.toDto(user);
assertEquals("Kevin", dto.getUsername());
assertEquals("ACTIVE", dto.getStatus());
}
}
這項測試驗證的是以下內容:
-
username段已正確映射。 -
enum值Status.ACTIVE被轉換為其String表示形式“ACTIVE”
值得注意的是,這種行為在MapStruct中開箱即用,無需任何額外配置。
5. 將日期映射到字串
處理日期欄位時,我們通常需要先將其轉換為格式化的字串表示形式,然後再透過 API 公開或在使用者介面中顯示MapStruct透過@Mapping註解中的dateFormat屬性來支援這種使用場景。
利用此屬性,我們可以定義MapStruct在映射過程中要套用的確切日期模式。
5.1. 定義域和DTO類
我們先來定義一個簡單的域實體,其中包含一個LocalDate欄位:
public class Event {
private String name;
private LocalDate eventDate;
}
接下來,我們定義一個對應的 DTO,其中eventDate欄位表示為String :
public class EventDTO {
private String name;
private String eventDate;
}
這裡, Event類別代表領域模型,而EventDTO是用於輸出的資料傳輸物件。主要差異在於,來源物件中的eventDate欄位是LocalDate類型,而目標物件中的 eventDate 欄位是格式化的String 。
5.2 建立映射器
現在,讓我們定義一個映射器接口,將Event轉換為EventDTO :
@Mapper
public interface EventMapper {
EventMapper INSTANCE = Mappers.getMapper(EventMapper.class);
@Mapping(source = "eventDate", target = "eventDate", dateFormat = "yyyy-MM-dd")
EventDTO toEventDTO(Event event);
}
讓我們來分析一下映射器:
-
dateFormat屬性指定所需的字串格式 -
MapStruct在編譯時產生必要的轉換邏輯 - 如果來源日期為
null,MapStruct會安全地將其在目標中對應到null
MapStruct使用DateTimeFormatter執行日期到字串的轉換。
5.3 驗證映射關係
現在讓我們編寫一個單元測試,以確保日期映射正確:
public class EventMapperUnitTest {
@Test
public void shouldMapLocalDateToFormattedString() {
LocalDate date = LocalDate.of(2025, 11, 10);
Event event = new Event("Tech Meetup", date);
EventDTO dto = EventMapper.INSTANCE.toEventDTO(event);
assertNotNull(dto);
assertEquals("Tech Meetup", dto.getName());
assertEquals("2025-11-10", dto.getEventDate());
}
}
此測試使用映射器中定義的相同格式準備一個LocalDate實例。映射完成後,映射器會確認事件名稱已正確映射,並且LocalDate欄位已轉換為預期的字串表示形式。
6. 處理空值
將值映射到String時,正確處理空值至關重要。如果沒有明確處理,我們的 DTO 中可能會出現空值,甚至在套用自訂映射邏輯時會遇到NullPointerExceptions 。
MapStruct提供了多種方法來控制如何處理null值,既可以全域控制,也可以在欄位層級控制。
6.1. 使用nullValueMappingStrategy
MapStruct允許我們使用nullValueMappingStrategy特性來定義如何處理空源物件。一個常用的選項是RETURN_DEFAULT ,它指示MapStruct在來源物件本身為空時傳回一個預設的目標實例。
讓我們修改PersonMapper來示範這一點:
@Mapper(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "age", defaultValue = "")
PersonDTO toDTO(Person person);
}
在此配置下,如果來源Person物件為null , MapStruct將傳回一個帶有預設值的非空PersonDTO 。此外,我們使用defaultValue控制映射時的預設輸出,而無需引入自訂轉換邏輯。
6.2. 自訂映射方法中的空值處理
當我們需要更精細地控制各個欄位的映射方式時,可以定義自訂映射方法。這在需要用有意義的預設值替換null值時尤其有用。
例如,讓我們將缺少的名稱對應到“Unknown”而不是null :
@Mapper(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "name", qualifiedByName = "mapName")
@Mapping(target = "age", defaultValue = "")
PersonDTO toDTO(Person person);
@Named("mapName")
default String mapName(String name) {
return name == null ? "Unknown" : name;
}
}
讓我們來詳細分析一下上面的修改:
-
@Named註解用於標記自訂方法,以便在映射中引用它。 -
mapName()方法會將null值替換為“Unknown” -
MapStruct在映射過程中會自動呼叫此方法。
透過這種方法,我們可以完全控制各個欄位在來源值為null的行為,同時保持映射邏輯集中化和可重複使用。
7. 結論
在本教程中,我們探討如何使用MapStruct將不同類型的資料對應到String 。
我們講解了簡單的數值轉換、 enum-to-string映射、 LocalDate欄位的格式化以及有效處理空值的策略。每個範例都示範了MapStruct如何產生類型安全的編譯時映射程式碼,從而減少樣板程式碼並消除潛在的執行時間錯誤。
透過利用dateFormat屬性、 nullValueMappingStrategy和自訂映射方法等功能,開發人員可以創建簡潔且可預測的 DTO 表示,以滿足其應用程式的需求。總而言之, MapStruct簡化了複雜的轉換,使您的映射邏輯更易於維護、更易讀且更易於測試。
在生產環境中使用這些映射之前,請確保 DTO 符合 API 要求。
原始碼已上傳至 GitHub 。