如何使用 MapStruct 將空字串映射到 Null
1.概述
在本教程中,我們將研究如何自訂 MapStruct 映射器以將空String
轉換為空值。我們將研究幾種選擇,每種選擇都提供不同程度的控制和客製化。
2. 範例對象
在我們開始之前,我們需要建立兩個物件來在我們的範例和測試之間進行映射。為了簡單起見,我們將使用Student
作為映射物件:
class Student {
String firstName;
String lastName;
}
為了將我們的物件映射到其中,讓我們建立一個Teacher
:
class Teacher {
String firstName;
String lastName;
}
我們在這裡把事情保持在非常基本的狀態;兩個類別都具有相同名稱的相同屬性,因此預設情況下,我們的映射器無需額外的註釋即可工作。
3. 所有字串的全域映射器
對於我們的第一個映射器,讓我們來看看一個涵蓋我們創建的每種情況和每種映射的解決方案。如果我們的應用程式總是想將String
轉換為空值,毫無例外,那麼這很有用。
對於所有範例來說,映射器的基本輪廓都是相同的。我們需要一個用@Mapper,
一個接口的實例,以便我們可以使用它,最後是我們的映射器方法:
@Mapper
interface EmptyStringToNullGlobal {
EmptyStringToNullGlobal INSTANCE = Mappers.getMapper(EmptyStringToNullGlobal.class);
Teacher toTeacher(Student student);
}
這已經適用於基本映射。但是,如果我們想將空String
轉換為空值,我們需要向介面添加另一個映射器:
String mapEmptyString(String string) {
return string != null && !string.isEmpty() ? string : null;
}
在這個方法中,我們告訴 MapStruct,如果處理的String
已經為空或為空,則傳回 null。否則,它會傳回原始String
。
MapStruct 將對遇到的所有String
使用此方法,因此如果我們在介面中添加更多映射器,它們也會自動使用它。只要我們一直想要這種行為,這個選項就很簡單,可自動重複使用,並且只需要最少的額外程式碼。它也是一個多功能的解決方案,因為它可以讓我們做比我們在這裡關注的轉換更多的事情。
為了示範這個工作原理,讓我們建立一個簡單的測試:
@Test
void givenAMapperWithGlobalNullHandling_whenConvertingEmptyString_thenOutputNull() {
EmptyStringToNullGlobal globalMapper = EmptyStringToNullGlobal.INSTANCE;
Student student = new Student("Steve", "");
Teacher teacher = globalMapper.toTeacher(student);
assertEquals("Steve", teacher.firstName);
assertNull(teacher.lastName);
}
在這個測試中,我們檢索了映射器的一個實例。然後,我們創建了一個Student
,名字是 Steve,中間名是一個空String
。使用我們的映射器後,我們可以看到創建的Teacher
物件的名字正如預期的那樣是 Steve。它還具有一個空的第二個名稱,證明映射器按照我們的需要工作。
4.使用@Condition
註釋
4.1. @Condition
的基本用法
我們將要研究的第二個選項是使用@Condition
註釋。此註釋允許我們建立 MapStruct 將呼叫的方法來檢查是否應該映射屬性。由於 MapStruct 會將未對應的欄位預設為 null,我們可以利用它來實現我們的目標。這次,我們的映射器介面看起來與以前相同,但是用新方法取代了先前的String
映射器:
@Mapper
interface EmptyStringToNullCondition {
EmptyStringToNullCondition INSTANCE = Mappers.getMapper(EmptyStringToNullCondition.class);
Teacher toTeacher(Student student);
@Condition
default boolean isNotEmpty(String value) {
return value != null && !value.isEmpty();
}
}
每次遇到要處理的String
時,MapStruct 都會呼叫此處的方法isNotEmpty()
。如果傳回false
,則不會對應該值,且目標物件中的欄位將為空。另一方面,如果傳回true
,則該值將被映射。
讓我們建立一個測試來確認它是否按預期工作:
@Test
void givenAMapperWithConditionAnnotationNullHandling_whenConvertingEmptyString_thenOutputNull() {
EmptyStringToNullCondition conditionMapper = EmptyStringToNullCondition.INSTANCE;
Student student = new Student("Steve", "");
Teacher teacher = conditionMapper.toTeacher(student);
assertEquals("Steve", teacher.firstName);
assertNull(teacher.lastName);
}
該測試與我們為所查看的最後一個選項編寫的測試幾乎相同。我們只是使用新的映射器並檢查是否獲得相同的結果。
4.2.檢查目標和來源屬性名稱
@Condition
註解選項比我們之前的全域映射器範例更具可自訂性。我們可以為isNotEmpty()
方法簽章新增兩個額外的註解參數, targetPropertyName
和sourcePropertyName
:
@Condition
boolean isNotEmpty(String value, @TargetPropertyName String targetPropertyName, @SourcePropertyName String sourcePropertyName) {
if (sourcePropertyName.equals("lastName")) {
return value != null && !value.isEmpty();
}
return true;
}
這些新參數為我們提供了要對應到的欄位名稱。我們可以使用它們對某些String
進行特殊處理,甚至可能將其他的 String 完全排除在我們的檢查之外。在這個範例中,我們特別尋找了一個名為lastName
的來源屬性,並且只有在找到它時才套用我們的檢查。如果我們想在大多數情況下將空String
應用於空映射並且只有少數例外,或者我們很少想應用它,則此選項很有用。
如果我們想要查找很多異常或字段,程式碼就會變得太混亂,我們應該尋找另一種方法。這個選項也僅限於創建的物件中的欄位為空或來源值;我們根本無法改變這個價值。
5. 使用表達式
最後,讓我們來看看最有針對性的選擇。我們可以在映射器中使用表達式來一次影響單一映射方法。因此,如果這不是我們想要廣泛使用的行為,那麼這種方法是最佳的。要使用表達式,我們只需將其新增至方法上的@Mapping
註解中。我們的映射器介面將如下所示:
@Mapper
public interface EmptyStringToNullExpression {
EmptyStringToNullExpression INSTANCE = Mappers.getMapper(EmptyStringToNullExpression.class);
@Mapping(target = "lastName", expression = "java(student.lastName.isEmpty() ? null : student.lastName)")
Teacher toTeacher(Student student);
}
這個選項給了我們很大的權力。我們定義了我們感興趣的目標字段,並提供了 Java 程式碼來告訴 MapStruct 如何映射它。我們的 Java 檢查lastName
欄位是否為空;如果是,則傳回 null,否則傳回原始lastName
。
這裡的好處和缺點是我們已經非常明確地說明這將影響哪些領域。 firstName
欄位根本不會被處理,更不用說我們可能定義的其他映射器中的任何String
了。因此,對於我們很少想要這樣映射String
的應用程式來說,這是一個理想的選擇。
最後,讓我們測試基於表達式的選項並確認它有效:
@Test
void givenAMapperUsingExpressionBasedNullHandling_whenConvertingEmptyString_thenOutputNull() {
EmptyStringToNullExpression expressionMapper = EmptyStringToNullExpression.INSTANCE;
Student student = new Student("Steve", "");
Teacher teacher = expressionMapper.toTeacher(student);
assertEquals("Steve", teacher.firstName);
assertNull(teacher.lastName);
}
再次,我們測試了同樣的事情,只是使用了我們的表達式映射器。再次, lastName
欄位會如預期對應到 null,而firstName
保持不變。
6. 結論
在本文中,我們研究了使用 MapStruct 將String
轉換為空值的三種選項。首先,我們看到,如果我們總是希望這種行為毫無例外,那麼使用全域映射器是一個簡單的選擇。然後,我們考慮使用@Condition
註釋來獲得類似的結果,但如果需要的話,可以選擇更多控制。最後,我們看到如果我們很少希望發生某種映射,那麼如何使用表達式來定位單一欄位。
與往常一樣,範例的完整程式碼可在 GitHub 上找到。