MapStruct 空值處理
1. 概述
MapStruct 是一個映射庫,它使工程師能夠以最小的努力處理複雜的映射場景。 MapStruct 的一個關鍵特性是預設值賦值,這使我們能夠為空值實作不同的填充策略。
在本文中,我們將示範如何在物件映射期間處理null值,方法是實作利用預設值賦值和其他 MapStruct 功能的映射器。
2. 模型
在開始編寫和配置映射器之前,我們需要範例中要映射的模型。我們的映射來源將是Order類別:
public class Order {
private String transactionId;
private List<String> orderItemIds;
private Payment payment;
// getters - setters - constructors
}
此外, Order物件包含Payment物件作為屬性,因此讓我們定義Payment類別:
public class Payment {
private String type;
private String amount;
// getters - setters - constructors
}
由於我們主要關注空值處理, OrderDto目標模型與來源模型完全相同。 OrderDto 是Order對應對象:
public class OrderDto {
private String transactionId;
private List<String> orderItemIds;
private PaymentDto payment;
// getters - setters - constructors
}
同樣, PaymentDto是Payment類別的鏡像:
public class PaymentDto {
private String type;
private Double amount;
// getters - setters - constructors
}
PaymentMapper是大多數Order Mapper 的基礎元件,因此在本節中介紹它以及其他先決條件至關重要:
@Mapper
public interface PaymentMapper {
PaymentDto toDto(Payment payment);
}
@Mapper註解指示 MapStruct 處理器產生一個實作了PaymentMapper介面的類別。編譯成功後,可以在專案的 `target` 資料夾中找到並檢查 MapStruct 的處理結果。
3. @AfterMapping 註解
改變映射器處理空值行為的一種方法是使用AfterMapping註解,它允許我們在映射器方法傳回映射物件之前對其進行後處理。通常,使用AfterMapping註解的方法可以接受映射來源、目標和上下文作為參數,以便應用所需的後處理。 OrderMapperWithAfterMapping OrderMapperWithAfterMapping了這種方法,這種方法最符合 Java 規格:
@Mapper(uses = PaymentMapper.class)
public interface OrderMapperWithAfterMapping {
OrderDto toDto(Order order);
@AfterMapping
default OrderDto postProcessing(@MappingTarget OrderDto orderDto) {
if (orderDto.getOrderItemIds() == null) {
orderDto.setOrderItemIds(new ArrayList<>());
}
if (orderDto.getTransactionId() == null) {
orderDto.setTransactionId("N/A");
}
return orderDto;
}
}
MapStruct 產生的toDto()方法會呼叫postProcessing()作為映射過程的最後一步:
@Override
public OrderDto toDto(Order order) {
if ( order == null ) {
return null;
}
OrderDto orderDto = new OrderDto();
orderDto.setPayment( paymentMapper.toDto( order.getPayment() ) );
orderDto.setTransactionId( order.getTransactionId() );
List list = order.getOrderItemIds();
if ( list != null ) {
orderDto.setOrderItemIds( new ArrayList( list ) );
}
OrderDto target = postProcessing( orderDto );
if ( target != null ) {
return target;
}
return orderDto;
}
在這裡, postProcessing()方法會在orderItemIds屬性為空時為其賦值一個空List ,並在 transactionId 屬性為 null 時為其賦值「N/A」字串。
4. 地圖示註功能
Mapping註解提供了透過defaultValue和defaultExpression屬性來處理空值的設定選項。實際上, OrderMapperWithDefault同時使用了這兩個屬性:
@Mapper(uses = PaymentMapper.class)
public interface OrderMapperWithDefault {
@Mapping(
source = "payment",
target = "payment",
defaultExpression = "java(new com.baeldung.dto.PaymentDto())"
)
@Mapping(
source = "transactionId",
target = "transactionId",
defaultValue = "N/A"
)
OrderDto toDto(Order order);
}
程式碼產生後,會得到一個toDto()函數,該函數會依照註解的指示處理空值:
@Override
public OrderDto toDto(Order order) {
if ( order == null ) {
return null;
}
OrderDto orderDto = new OrderDto();
if ( order.getPayment() != null ) {
orderDto.setPayment( paymentMapper.toDto( order.getPayment() ) );
}
else {
orderDto.setPayment( new com.baeldung.dto.PaymentDto() );
}
if ( order.getTransactionId() != null ) {
orderDto.setTransactionId( order.getTransactionId() );
}
else {
orderDto.setTransactionId( "N/A" );
}
List list = order.getOrderItemIds();
if ( list != null ) {
orderDto.setOrderItemIds( new ArrayList( list ) );
}
return orderDto;
}
根據註解配置的指示,產生的 MapStruct 實作會在 payment 或 transactionId 屬性的值為 null 時初始化該屬性的值。
5. 空值檢查
為了理解為什麼以及何時應用空值檢查,我們需要先描述映射方法如何處理它們的參數。預設情況下,映射方法會為每個物件屬性呼叫後續的映射函數,而不會檢查來源屬性是否為空。這是因為產生的映射方法會預先檢查它們的參數是否為空。
要更改此行為,我們需要配置 ` nullValueCheckStrategy選項。 `nullValueCheckStrategy` 選項的預設值為ON_IMPLICIT_CONVERSIONS nullValueCheckStrategy**使用ON_IMPLICIT_CONVERSIONS註解的方法僅對直接映射或透過類型轉換映射的屬性引入空值檢查**。
PaymentMapperImpl範例說明了映射器執行類型轉換時如何進行空值檢查:
public class PaymentMapperImpl implements PaymentMapper {
public PaymentDto toDto(Payment payment) {
if (payment == null) {
return null;
} else {
PaymentDto paymentDto = new PaymentDto();
paymentDto.setType(payment.getType());
if (payment.getAmount() != null) {
paymentDto.setAmount(Double.parseDouble(payment.getAmount()));
}
return paymentDto;
}
}
}
實際上,映射方法會檢查amount來源屬性是否為空,以避免拋出NullPointerException異常。為了強制對每個屬性進行空值檢查,我們需要將 ALWAYS 值設定為nullValueCheckStrategy選項。為了示範此配置,我們來編寫AlwaysNullCheckPaymentMapper :
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface AlwaysNullCheckPaymentMapper {
PaymentDto toDto(Payment payment);
}
PaymentMapper和AlwaysNullCheckPaymentMapper之間的唯一區別在於nullValueCheckStrategy設定。產生的AlwaysNullCheckPaymentMapperImpl除了檢查amount屬性外,還會檢查type屬性是否為 null 值:
public class AlwaysNullCheckPaymentMapperImpl implements AlwaysNullCheckPaymentMapper {
public PaymentDto toDto(Payment payment) {
if (payment == null) {
return null;
} else {
PaymentDto paymentDto = new PaymentDto();
if (payment.getType() != null) {
paymentDto.setType(payment.getType());
}
if (payment.getAmount() != null) {
paymentDto.setAmount(Double.parseDouble(payment.getAmount()));
}
return paymentDto;
}
}
}
6. 結論
本文示範了使用 MapStruct 處理空值的不同方法。此外,我們也深入探討了 MapStruct 註解所公開的配置,以展示該程式庫的靈活性。
和往常一樣,程式碼可以在 GitHub 上找到。