mapstruct核心技術學習
- 簡介
- 入門案例
- maven依賴
- IDEA插件
- 單一對象轉換
- 測試結果
- mapping屬性
- Spring注入的方式
- 測試
- 集合的映射
- set類型的映射
- 測試
- map類型的映射
- 測試
- @MapMapping
- keyDateFormat
- valueDateFormat
- 枚舉映射
- 基礎入門
簡介
在工作中,我們經常要進行各種對象之間的轉換。
PO: persistent object持久對象,對應數據庫中的一條
VO: view object表現層對象,最終返回給前端的對象
DTO:data transfer object數據傳輸對象,如dubbo服務之間的傳輸的對象
po、vo、dto的詳細介紹
如果這些對象的屬性名相同還好,可以使用如下工具類賦值
Spring BeanUtils
Cglib BeanCopier
避免使用Apache BeanUtils,性能較差
如果屬性名不同呢?如果是將多個PO對象合并成一個VO對象呢?好在有MapStruct,可以幫助我們快速轉換
mapstruct官網
mapstruct技術文檔
入門案例
maven依賴
<properties><java.version>1.8</java.version><org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
<dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></dependency>
</dependencies>
IDEA插件
IDEA中搜索"MapStruct Support"插件,進行安裝,安裝成功后重啟IDEA。
單一對象轉換
import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class CarDTO {private String make;private int seatCount;private String type;
}
import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class Car {private String make;private int numberOfSeats;
}
import com.example.demo.dto.CarDTO;
import com.example.demo.po.Car;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;@Mapper
public interface CarMapper {CarMapper instance = Mappers.getMapper(CarMapper.class);/*** 表達式需要自動提示的話,需要安裝IDEA插件mapstruct support* @param car* @return*/@Mapping(source = "numberOfSeats",target = "seatCount")@Mapping(target = "type",expression = "java(car.getMake())")CarDTO carToCarDto(Car car);
}
import com.example.demo.dto.CarDTO;
import com.example.demo.mapper.CarMapper;
import com.example.demo.po.Car;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {DemoApplication.class})
public class ApplicationTest {@Testpublic void test() {CarMapper instance = CarMapper.instance;Car car = new Car();car.setMake("中國").setNumberOfSeats(1000);CarDTO carDTO = instance.carToCarDto(car);System.out.println(carDTO);}
}
測試結果
項目結構圖
在target文件夾下生成了CarMapperImpl.java
package com.example.demo.mapper;import com.example.demo.dto.CarDTO;
import com.example.demo.po.Car;
import javax.annotation.Generated;@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2023-11-08T23:35:28+0800",comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_131 (Oracle Corporation)"
)
public class CarMapperImpl implements CarMapper {@Overridepublic CarDTO carToCarDto(Car car) {if ( car == null ) {return null;}CarDTO carDTO = new CarDTO();carDTO.setSeatCount( car.getNumberOfSeats() );carDTO.setMake( car.getMake() );carDTO.setType( car.getMake() );return carDTO;}
}
mapping屬性
/*** @Mappings 一組映射關系,值為一個數組,元素為@Mapping* @Mapping 一一對應關系* source:源屬性* target:目標屬性,賦值的過程是把源屬性賦值給目標屬性* dateFormat:用于源屬性是Date,轉換為String* numberFormat:用戶數值類型與String類型之間的轉* constant: 常量* expression:使用表達式進行屬性之間的轉換* ignore:忽略某個屬性的賦值* qualifiedByName: 自定義的方法賦值* defaultValue:默認值* @defaultExpression 如果源數據沒有設置的時候,可以指定相關表達式進行處理* 基本數據類型與包裝類可以自動映射* @MappingTaget 用在方法參數的前面,使用此注解,源對象同時也會作為目標對象,用于更新* @InheritConfiguration 指定映射方法* @InheritInverseConfiguration 表示方法繼承相應的反向方法的反向配置* @param car 入參* @return 返回結果*/
package com.example.demo.entity;import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class CarBrand {private String carBrand;
}
package com.example.demo.entity;import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class Brand {private String brandName;}
package com.example.demo.entity;import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class CarDto {private String make;private int seatCount;private String type;private CarBrand carBrand;private String date;private String price;private String extral;
}
package com.example.demo.entity;import lombok.Data;
import lombok.experimental.Accessors;import java.math.BigDecimal;
import java.util.Date;@Data
@Accessors(chain = true)
public class Car {private String make;private int numberOfSeats;private Brand brand;private Date date;private BigDecimal price;}
package com.example.demo.entity;import org.mapstruct.*;import static org.mapstruct.MappingConstants.ComponentModel.SPRING;/*** @Mapper 表示該接口作為映射接口,編譯時MapStruct處理器的入口* componentModel 主要是指定實現類的類型,一般用到兩個* default:默認,可以通過 Mappers.getMapper(Class) 方式獲取實例對象* spring:在接口的實現類上自動添加注解 @Component,可通過 @Autowired 方式注入* uses 外部引入的轉換類*/
@Mapper(componentModel = SPRING)
public interface CarMapper {/*** @Mappings 一組映射關系,值為一個數組,元素為@Mapping* @Mapping 一一對應關系* source:源屬性* target:目標屬性,賦值的過程是把源屬性賦值給目標屬性,當目標屬性和源屬性一致時候,source和target可以省略不寫* dateFormat:用于源屬性是Date,轉換為String* numberFormat:用戶數值類型與String類型之間的轉* constant: 常量* expression:使用表達式進行屬性之間的轉換* ignore:忽略某個屬性的賦值* qualifiedByName: 自定義的方法賦值* defaultValue:默認值* @defaultExpression 如果源數據沒有設置的時候,可以指定相關表達式進行處理* 基本數據類型與包裝類可以自動映射* @MappingTaget 用在方法參數的前面,使用此注解,源對象同時也會作為目標對象,用于更新* @InheritConfiguration 指定映射方法* @InheritInverseConfiguration 表示方法繼承相應的反向方法的反向配置* @param car 入參* @return 返回結果*/@Mappings({@Mapping(source = "date",target = "date",dateFormat = "yyyy-MM-dd HH:mm:ss"),@Mapping(source = "price",target = "price",numberFormat = "0.00"),@Mapping(source = "numberOfSeats",target = "seatCount"),@Mapping(target = "type",constant = "hello type"),@Mapping(source = "brand",target = "carBrand",qualifiedByName = {"brand2CarBrandV2"})})CarDto carToCarDtoV2(Car car);/*** @Named 定義類/方法的名稱* @param brand* @return*/@Named("brand2CarBrandV2")@Mappings({@Mapping(source = "brandName",target = "carBrand")})CarBrand brand2CarBrandV2(Brand brand);
}
package com.example.demo;import com.example.demo.entity.Brand;
import com.example.demo.entity.Car;
import com.example.demo.entity.CarDto;
import com.example.demo.entity.CarMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.math.BigDecimal;
import java.util.Date;@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {DemoApplication.class})
public class CarMapperTest {@Autowiredprivate CarMapper carMapper;@Testpublic void test() {Car car = new Car();car.setMake("from source").setNumberOfSeats(100).setBrand(new Brand().setBrandName("保密")).setPrice(BigDecimal.valueOf(100.12345)).setDate(new Date());CarDto dto = carMapper.carToCarDtoV2(car);System.out.println(dto);}
}
測試結果,輸出如下
CarDto(make=from source, seatCount=100, type=hello type, carBrand=CarBrand(carBrand=保密), date=2023-11-11 20:22:49, price=100.12, extral=null)
Spring注入的方式
中聲明INSTANCE的方式來進行調用之外,MapStruct也同時支持Spring的依賴注入機制
package com.example.MapStructDemo.dto;import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class CarDto {private String manufacturer;private int seatCount;
}
package com.example.MapStructDemo.po;import lombok.Data;
import lombok.experimental.Accessors;@Data
@Accessors(chain = true)
public class Car {private String make;private int numberOfSeats;
}
package com.example.MapStructDemo.mapper;import com.example.MapStructDemo.dto.CarDto;
import com.example.MapStructDemo.po.Car;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;import static org.mapstruct.MappingConstants.ComponentModel.SPRING;/*** @Mapper 只有在接口加上這個注解, MapStruct 才會去實現該接口* @Mapper 里有個 componentModel 屬性,主要是指定實現類的類型,一般用到兩個* default:默認,可以通過 Mappers.getMapper(Class) 方式獲取實例對象* SPRING:在接口的實現類上自動添加注解 @Component,可通過 @Autowired 方式注入*/
@Mapper(componentModel = SPRING)
public interface CarMapper {@Mapping(target = "manufacturer",source = "make")@Mapping(target = "seatCount",source = "numberOfSeats")CarDto carToCarDto(Car car);}
測試
package com.example.MapStructDemo;import com.example.MapStructDemo.dto.CarDto;
import com.example.MapStructDemo.mapper.CarMapper;
import com.example.MapStructDemo.po.Car;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class ApplicationTests {@Autowiredprivate CarMapper carMapper;@Testpublic void contextLoads() {Car car = new Car();car.setMake("中國").setNumberOfSeats(1000);CarDto carDto = carMapper.carToCarDto(car);System.out.println("測試結果");System.out.println(carDto);}}
集合的映射
set類型的映射
package com.example.MapStructDemo.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.Set;
import static org.mapstruct.MappingConstants.ComponentModel.SPRING;@Mapper(componentModel = SPRING)
public interface CarMapper {/*** 集合的映射* @param set 入參* @return Set<String>*/Set<String> integerSetToStringSet(Set<Integer> set);
}
CarMapper
的實現類CarMapperImpl
,會生成如下代碼,集合set為null
的時候,默認返回null
測試
package com.example.MapStructDemo;import com.example.MapStructDemo.mapper.CarMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.HashSet;
import java.util.Set;@SpringBootTest
public class ApplicationTests {@Autowiredprivate CarMapper carMapper;@Testpublic void test(){Set<Integer> set = new HashSet<>();for (int i=0;i<10;i++){set.add(i);}Set<String> strings = carMapper.integerSetToStringSet(set);System.out.println("集合類型的測試");strings.forEach(System.out::println);}
}
測試結果如下:
map類型的映射
package com.example.MapStructDemo.mapper;import org.mapstruct.MapMapping;
import org.mapstruct.Mapper;import java.util.Date;
import java.util.Map;import static org.mapstruct.MappingConstants.ComponentModel.SPRING;@Mapper(componentModel = SPRING)
public interface SourceTargetMapper {/*** map類型的映射* @param source 入參* @return Map<String, String>* map中value的值是Date類型的轉換為String類型*/@MapMapping(valueDateFormat = "dd.MM.yyyy")Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
map映射的實現類SourceTargetMapperImpl
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package com.example.MapStructDemo.mapper;import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.stereotype.Component;@Component
public class SourceTargetMapperImpl implements SourceTargetMapper {public SourceTargetMapperImpl() {}public Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source) {if (source == null) {return null;} else {Map<String, String> map = new LinkedHashMap(Math.max((int)((float)source.size() / 0.75F) + 1, 16));Iterator var3 = source.entrySet().iterator();while(var3.hasNext()) {Map.Entry<Long, Date> entry = (Map.Entry)var3.next();String key = (new DecimalFormat("")).format(entry.getKey());String value = (new SimpleDateFormat("dd.MM.yyyy")).format((Date)entry.getValue());map.put(key, value);}return map;}}
}
測試
package com.example.MapStructDemo;import com.example.MapStructDemo.mapper.SourceTargetMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@SpringBootTest
public class SourceTargetMapperTests {@Autowiredprivate SourceTargetMapper sourceTargetMapper;@Testpublic void test() {Map<Long, Date> map = new HashMap<>();map.put(1L, new Date());System.out.println(map);System.out.println("map類型的映射");Map<String, String> result = sourceTargetMapper.longDateMapToStringStringMap(map);System.out.println(result);}
}
測試結果
@MapMapping
配置的是Map<String,String>
和Map<Long,Date>
之間的轉換
keyDateFormat
map
中key
的類型是從Date
到String
類型的轉換
valueDateFormat
map中value的類型從Date
到String
類型的轉換
枚舉映射
基礎入門
package com.example.MapStructDemo.enums;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum OrderType {EXTRA,STANDARD,NORMAL
}
package com.example.MapStructDemo.enums;import lombok.AllArgsConstructor;
import lombok.Getter;@AllArgsConstructor
@Getter
public enum ExternalOrderType {SPECIAL,DEFAULT
}
package com.example.MapStructDemo.mapper;import com.example.MapStructDemo.enums.ExternalOrderType;
import com.example.MapStructDemo.enums.OrderType;
import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;import static org.mapstruct.MappingConstants.ComponentModel.SPRING;@Mapper(componentModel = SPRING)
public interface OrderMapper {/*** 枚舉類型映射* @param orderType 入參* @return ExternalOrderType*/@ValueMappings({@ValueMapping(target = "SPECIAL",source = "EXTRA"),@ValueMapping(target = "DEFAULT",source = "STANDARD"),@ValueMapping(target = "DEFAULT",source = "NORMAL")})ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}
OrderMapper的實現類
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package com.example.MapStructDemo.mapper;import com.example.MapStructDemo.enums.ExternalOrderType;
import com.example.MapStructDemo.enums.OrderType;
import org.springframework.stereotype.Component;@Component
public class OrderMapperImpl implements OrderMapper {public OrderMapperImpl() {}public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) {if (orderType == null) {return null;} else {ExternalOrderType externalOrderType;switch (orderType) {case EXTRA:externalOrderType = ExternalOrderType.SPECIAL;break;case STANDARD:externalOrderType = ExternalOrderType.DEFAULT;break;case NORMAL:externalOrderType = ExternalOrderType.DEFAULT;break;default:throw new IllegalArgumentException("Unexpected enum constant: " + orderType);}return externalOrderType;}}
}
package com.example.MapStructDemo;import com.example.MapStructDemo.enums.ExternalOrderType;
import com.example.MapStructDemo.enums.OrderType;
import com.example.MapStructDemo.mapper.OrderMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class OrderMapperTest {@Autowiredprivate OrderMapper orderMapper;@Testpublic void test1() {System.out.println("測試結果");OrderType orderType = OrderType.EXTRA;ExternalOrderType externalOrderType = orderMapper.orderTypeToExternalOrderType(orderType);System.out.println(externalOrderType);}
}
測試結果