如何在 Mapstruct 中進行嵌套映射?
1.概述
在本教程中,我們將利用 MapStruct 庫將來源 POJO 中的嵌套屬性對應到目標 POJO 的屬性。同樣,我們可以使用本教程中討論的相同技術將來源實體的屬性值對應到目標實體的巢狀屬性值。
Mapstruct 使用註解來幫助定義 POJO 屬性之間的對應。此外,其 Maven 插件透過讀取註解中定義的元資料自動產生映射器實用程式。此外,該庫還允許定義自訂映射器實用程序,以支援細粒度控制。
我們將示範如何使用 MapStruct 函式庫將巢狀屬性從分層來源實體對應到平面目標實體。
2.用例
讓我們考慮一個來源實體Order,
和一個目標實體OrderDto
,以示範 MapStruct 庫執行巢狀映射的能力:
來源實體Order
表示具有巢狀結構的複雜對象,包括Customer
和Product:
public class Order {
private Customer customer;
private Product product;
//..Standard getters and setters
}
public class Customer {
private String name;
private Address address;
//..Standard getters and setters
}
public class Product {
private String name;
private double price;
//..Standard getters and setters
}
此外, Customer
實體具有 Address 類型的address
屬性Address:
public class Address {
private String city;
private String zipCode;
//..Standard getters and setters
}
目標實體OrderDto
是Order
實體的簡化扁平版本。它包含customerName
、 customerCity
、 customerZipCode
、 productName
和productPrice
等字段,這些字段是Order
實體的嵌套字段:
public class OrderDto {
private String customerName;
private String customerCity;
private String customerZipCode;
private String productName;
private double productPrice;
//..Standard getters and setters
}
此外,在接下來的部分中,我們將學習使用 MapStruct 函式庫從Order
物件建立OrderDto
。
3.使用@Mapping
註解映射巢狀屬性
只需要一對一映射並且不需要自訂的情況可以使用@Mapping
註解簡單處理。
首先,我們必須依照 MapStruct 函式庫的約定定義用@Mapper
註解修飾的映射器介面:
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
@Mapping(source = "customer.name", target = "customerName")
@Mapping(source = "product.name", target = "productName")
@Mapping(source = "product.price", target = "productPrice")
@Mapping(source = "customer.address.city", target = "customerCity")
@Mapping(expression = "java(order.getCustomer().getAddress().getZipCode())",
target = "customerZipCode")
OrderDto orderToOrderDto(Order order);
}
在OrderMapper
介面中, orderToOrderDto()
方法將Order
物件轉換為OrderDto
物件。映射器方法上的@Mapping
註解用於定義從Order
物件屬性(包括嵌套屬性)到目標OrderDto
屬性的映射。
此外,我們使用點符號來存取Order#customer
和Order#product
等屬性的屬性。同樣,我們存取Customer#address
屬性的city
屬性。此外,我們還可以自由使用Java 表達式。因此,我們用它來填充OrderDto#customerZipCode
屬性。
最後,當我們建構程式碼時,Maven外掛程式MapStruct Processor會產生OrderMapperImpl
類別:
public class OrderMapperImpl implements OrderMapper {
@Override
public OrderDto orderToOrderDto(Order order) {
if ( order == null ) {
return null;
}
OrderDto orderDto = new OrderDto();
orderDto.setCustomerName( orderCustomerName( order ) );
orderDto.setProductName( orderProductName( order ) );
orderDto.setProductPrice( orderProductPrice( order ) );
orderDto.setCustomerCity( orderCustomerAddressCity( order ) );
orderDto.setCustomerZipCode( order.getCustomer().getAddress().getZipCode() );
return orderDto;
}
private String orderCustomerName(Order order) {
Customer customer = order.getCustomer();
if ( customer == null ) {
return null;
}
return customer.getName();
}
private String orderCustomerAddressCity(Order order) {
Customer customer = order.getCustomer();
if ( customer == null ) {
return null;
}
Address address = customer.getAddress();
if ( address == null ) {
return null;
}
return address.getCity();
}
//..Other private methods
}
Maven 外掛程式會解析Mapping
註解,並智慧產生orderToOrderDto()
和其他支援私有方法,以擷取Order
物件的巢狀屬性。但是,**填入****OrderDto#customerZipCode attribute**
會直接使用註解中的 Java 表達式。與其他情況不同,這裡我們沒有進行任何空值檢查。
接下來,讓我們呼叫產生的映射器類別:
void givenOrder_whenMapToOrderDto_thenMapNestedAttributes() {
Order order = createSampleOrderObject();
OrderDto orderDto = OrderMapper.INSTANCE.orderToOrderDto(order);
assertEquals("John Doe", orderDto.getCustomerName());
assertEquals("New York", orderDto.getCustomerCity());
assertEquals("10001", orderDto.getCustomerZipCode());
assertEquals("Laptop", orderDto.getProductName());
assertEquals(1200.00, orderDto.getProductPrice());
}
createSampleOrder()
方法用於建立一個範例Order
對象,該物件作為參數傳遞給映射器orderToOrderDto()
方法。最後,我們驗證所有嵌套屬性是否正確映射到目標OrderDto
物件。
4. 使用抽象映射器來映射巢狀屬性
@Mapping
註解可能無法提供足夠的彈性來實現映射過程中所需的結果。對於這種情況,我們可能必須使用抽象映射器類別:
@Mapper
public abstract class AbstractOrderMapper {
public static final AbstractOrderMapper INSTANCE = Mappers.getMapper(AbstractOrderMapper.class);
public OrderDto orderToOrderDto(Order order) {
OrderDto orderDto = applyCustomMappings(order);
orderDto = mapCustomer(order);
mapProduct(order, orderDto);
return orderDto;
}
@Mapping(source = "customer.name", target = "customerName")
@Mapping(source = "customer.address.city", target = "customerCity")
@Mapping(source = "customer.address.zipCode", target = "customerZipCode")
protected abstract OrderDto mapCustomer(Order order);
@Mapping(source = "product.name", target = "productName")
@Mapping(source = "product.price", target = "productPrice")
protected abstract void mapProduct(Order order, @MappingTarget OrderDto orderDto);
}
首先, orderToOrderDto()
方法呼叫applyCustomMappings()
方法來初始化OrderDto
物件。此外, applyCustomMappings()
可能會實作一些邏輯或呼叫下游服務來建立OrderDto
物件。之後,我們呼叫mapCustomer()
和mapProduct()
等抽象方法。這些抽象方法具有基本的@Mapping
註解,類似於我們在上一節中討論過的註解。它們有助於從Order
物件更新OrderDto
物件中的customer
屬性和product
屬性。
在其他場景下,我們可以選擇哪些方法保持抽象,哪些方法需要具體實作以實現客製化。此外,我們可以使用其他 MapStruct 特性或@ObjectFactory
等註解來實現需要細粒度控制的客製化。
接下來,讓我們建立類別來產生AbstractOrderMapperImpl
類別:
public class AbstractOrderMapperImpl extends AbstractOrderMapper {
@Override
protected OrderDto mapCustomer(Order order) {
if ( order == null ) {
return null;
}
OrderDto orderDto = new OrderDto();
orderDto.setCustomerName( orderCustomerName( order ) );
orderDto.setCustomerCity( orderCustomerAddressCity( order ) );
orderDto.setCustomerZipCode( orderCustomerAddressZipCode( order ) );
return orderDto;
}
@Override
protected void mapProduct(Order order, OrderDto orderDto) {
if ( order == null ) {
return;
}
orderDto.setProductName( orderProductName( order ) );
orderDto.setProductPrice( orderProductPrice( order ) );
}
private String orderCustomerName(Order order) {
Customer customer = order.getCustomer();
if ( customer == null ) {
return null;
}
return customer.getName();
}
//..Other generated private methods deriving the attribute
//..values from the nested properties of Order object
}
Maven 外掛程式解析了mapCustomer()
和mapProduct()
方法上@Mapping
註解的元數據,並產生了它們的實作。從父類別AbstractOrderMapper
繼承的AbstractOrderMapperImpl#orderToOrderDto()
方法會呼叫這些已實作的方法。
最後,我們現在可以運行AbstractOrderMapper#orderToOrderDto()
方法並驗證映射:
void givenOrder_whenMapToOrderDto_thenMapNestedAttributesWithAbstractMapper() {
Order order = createSampleOrderObject();
OrderDto orderDto = AbstractOrderMapper.INSTANCE.orderToOrderDto(order);
assertEquals("John Doe", orderDto.getCustomerName());
assertEquals("New York", orderDto.getCustomerCity());
assertEquals("10001", orderDto.getCustomerZipCode());
assertEquals("Laptop", orderDto.getProductName());
assertEquals(1200.00, orderDto.getProductPrice());
}
首先,我們透過呼叫createSampleOrderObject()
方法來建立了範例Order
物件。然後,我們實例化了AbstractOrderMapper
實作類別。接下來,我們將範例OrderDto
物件傳遞給AbstractOrderMapper#orderToOrderDto()
方法。最後,我們驗證了OrderDto
物件的各個屬性的值。
5. 結論
在本文中,我們學習如何將來源實體的嵌套屬性對應到目標實體。這是資料轉換應用中經常遇到的重要需求。因此,它是 MapStruct 庫的一個重要特性。此外,它還具備自訂處理複雜場景的能力,這使得它非常強大。
與往常一樣,本文中使用的源代碼可在 GitHub 上找到。