org.mapstruct.mapstruct 類型轉換工具


參考文檔:MapStruct 1.3.0.Final參考指南 (kailing.pub)
官方:https://mapstruct.org/
官方案例 github項目:https://github.com/mapstruct/mapstruct-examples

學習版本 :1.4.1.Final

各工具包-轉換性能對比:圖片來源知乎大佬
image

引入依賴

<!-- 用於生成類型安全 Bean 映射器的注釋處理器 -->
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.4.1.Final</version>
</dependency>

<!-- 測試依賴 非必須 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

第一個案例

實體類
@Data
public class Cat {
    private Integer id_cat;// 測試 【字段名稱不同】 的處理
    private Integer age; // age name 為 【基本數據類型】 字段
    private String name;
    private Date birthdate;// 【時間類型】
    private Master master; // 【引用數據類型】 - 動物的主人
}
@Data
public class CatDto {
    private Integer id;
    private Integer age;
    private String name;
    private Date birthdate;
    private MasterDto masterDto;
}
@Data
public class Master {
    private String name;
    private Integer age;
}
@Data
public class MasterDto {
    private String name;
    private Integer age;
}
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper(uses = {MasterMapper.class}) // 引入嵌套的對象 - 【不引入會造成引用數據類型為淺拷貝】
public interface CatMapper {
    // Mappers工廠 創建 實例
    CatMapper INSTANCE = Mappers.getMapper(CatMapper.class);
    CatDto catToCatDto(Cat cat);
}

@Mapper
public interface MasterMapper {
    MasterMapper INSTANCE = Mappers.getMapper(MasterMapper.class);
    MasterDto masterToMasterDto(Master master);
}
調用
public class Test_01 {
    public static void main(String[] args) {

        Cat cat = Test_01.newInstanceCat();
        System.out.println(cat.toString());
        System.out.println(cat.getMaster().hashCode());

        CatDto catDto = CatMapper.INSTANCE.catToCatDto(cat);

        System.out.println(catDto.toString());
        System.out.println(catDto.getMasterDto().hashCode());
    }
    public static Master newInstanceMaster() {
        Master master = new Master();
        master.setName("小輝");
        master.setAge(18);
        return master;
    }
    public static Cat newInstanceCat() {
        Cat cat = new Cat();
        cat.setId_cat(1);
        cat.setName("小花貓");
        cat.setAge(3);
        cat.setMaster(Test_01.newInstanceMaster());
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date birthdate = formatter.parse("2020-01-01");
            cat.setBirthdate(birthdate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return cat;
    }
}

映射器使用方式

1. 映射器 + Mappers工廠 (推薦使用-簡單易懂)

import org.mapstruct.Mapper;
@Mapper 
public interface CarMapper {
     CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
     CarDto carToCarDto(Car car);
}

// 調用
Car car = ...;
CarDto dto = CarMapper.INSTANCE.carToCarDto( car );

2. 基於容器 的 "cdi","spring" 模式

spring模式案例, cdi模式測試時java類缺失(個人感覺不會使用,沒有深究)

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper(componentModel = "spring")
public interface CatMapper {
	CatDto catToCatDto(Cat cat);
}

// 注入到spring容器中Bean名稱“catMapperImpl”,這里為注入示例
@Autowired
CatMapper catMapper;

映射器編寫

1. 基礎映射器

  • @Mapper 標記為映射器,
  • @Mapping 標記對象中的字段:source 源對象字段 ,target 目標字段
@Mapper
public interface CarMapper {

    @Mapping(source = "make", target = "manufacturer")
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);

    @Mapping(source = "name", target = "fullName")
    PersonDto personToPersonDto(Person person);
}

2. 自定義方法 不常用

自定義可以直接使用表達式來制定

這里付一個官方案例,可忽略
@Mapper
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    @Mapping( target = "key", ignore = true )
    @Mapping( target = "descriptionArticle1", source = "brush.description" )
    @Mapping( target = "descriptionArticle2", source = "paste.description" )
    CombinedOfferingEntity toEntity(Toothbrush brush, ToothPaste paste, @Context ArticleRepository repo);

    @ObjectFactory
    default <T extends Entity> T lookup(Toothbrush brush, ToothPaste paste, @Context ArticleRepository repo,
        @TargetType Class<T> targetType ) {
        ComposedKey key = new ComposedKey(brush.getName(), paste.getName() );
        CombinedOfferingEntity entity = repo.lookup( key );
        if ( entity == null ) {
            entity = new CombinedOfferingEntity();
        }
        return (T) entity;
    }
}
public class SourceTargetMapperTest {
    @Test
    public void testToTarget() {

        ToothPaste paste = new ToothPaste();
        paste.setName( "Paradontax" );
        paste.setDescription( "with active enzimes" );

        Toothbrush brush = new Toothbrush();
        brush.setName( "Oral-B" );
        brush.setDescription( "rotating head" );

        ArticleRepository repo = new ArticleRepository();

        CombinedOfferingEntity entity = SourceTargetMapper.MAPPER.toEntity( brush, paste, repo );

        //results
        assertThat( entity ).isNotNull();
        assertThat( entity.getKey() ).isNotNull();
        assertThat( entity.getKey().getName1() ).isEqualTo( "Oral-B" );
        assertThat( entity.getKey().getName2() ).isEqualTo( "Paradontax" );
        assertThat( entity.getDescriptionArticle1() ).isEqualTo( "rotating head" );
        assertThat( entity.getDescriptionArticle2() ).isEqualTo( "with active enzimes" );
    }
}
public class ArticleRepository {
    public CombinedOfferingEntity lookup(ComposedKey key) {
        // do some DB lookups here.
        CombinedOfferingEntity entity = new CombinedOfferingEntity();
        entity.setKey( key );
        return entity;
    }
}
public interface Entity {
    ComposedKey getKey();
}
public class ComposedKey {
    private final String name1;
    private final String name2;
    public ComposedKey(String name1, String name2) {
        this.name1 = name1;
        this.name2 = name2;
    }
    public String getName1() {
        return name1;
    }
    public String getName2() {
        return name2;
    }
}
public class CombinedOfferingEntity implements Entity {
    private ComposedKey key;
    private String descriptionArticle1;
    private String descriptionArticle2;
    @Override
    public ComposedKey getKey() {
        return key;
    }
    public void setKey(ComposedKey key) {
        this.key = key;
    }
    public String getDescriptionArticle1() {
        return descriptionArticle1;
    }
    public void setDescriptionArticle1(String descriptionArticle1) {
        this.descriptionArticle1 = descriptionArticle1;
    }
    public String getDescriptionArticle2() {
        return descriptionArticle2;
    }
    public void setDescriptionArticle2(String descriptionArticle2) {
        this.descriptionArticle2 = descriptionArticle2;
    }
}
public class Toothbrush {
    private String name;
    private String description;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}
public class ToothPaste {
    private String name;
    private String description;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

3. 多源映射(入參)

  • 源對象出現相同字段時必須用@Mapping指定
@Mapper
public interface AddressMapper {

    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "address.houseNo", target = "houseNumber")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}
// 特殊用法
@Mapper
public interface AddressMapper {

    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "hn", target = "houseNumber") // 這里可以直接注入到指定字段中
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}

4. 傳入目標對象

  • MappingTarget 用了標記目標對象
@Mapper
public interface CarMapper {
    void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}

5. 自定義邏輯

  • 見下 官方案例 1

6. map對象 映射類

public interface SourceTargetMapper {

    @MapMapping(valueDateFormat = "dd.MM.yyyy")
    Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}

7. 枚舉映射

@Mapper
public interface SpecialOrderMapper {

    SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class );

    @ValueMappings({
        @ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
        @ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
        @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
    })
    ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
}

8. 高級映射

    // 1.默認值和常量
    @Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
    @Mapping(target = "longProperty", source = "longProp", defaultValue = "-1")
    @Mapping(target = "stringConstant", constant = "Constant Value")
    @Mapping(target = "integerConstant", constant = "14")
    @Mapping(target = "longWrapperConstant", constant = "3001")
    @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014")
    @Mapping(target = "stringListConstants", constant = "jack-jill-tom")
    Target sourceToTarget(Source s);

    // 2. 表達式
    // @Mapper( imports = TimeAndFormat.class )   // 可以結合 Mapper 引入類
    // @Mapping(target = "timeAndFormat", 
    //     expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")
    @Mapping(target = "timeAndFormat",
         expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
    Target sourceToTarget(Source s);

    // 3. 默認表達式
    // 當 sourceId 為null時,會使用默認表達式
    @Mapping(target="id", source="sourceId", defaultExpression = "java( java.util.UUID.randomUUID().toString() )")
    Target sourceToTarget(Source s);

官方案例

1. 自定義邏輯處理 Mapping#qualifiedBy

    /**
     * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
     * mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
     * error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
     * <p>
     * Note that {@link #defaultValue()} usage will also be converted using this qualifier.
     *
     * @return the qualifiers
     * @see Qualifier
     */
    Class<? extends Annotation>[] qualifiedBy() default { };
自定義邏輯處理(枚舉)
// 用於標識
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface LastElement {}

@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface FirstElement {}

// 具體邏輯類
public class IterableNonInterableUtil {

    @FirstElement
    public <T> T first( List<T> in ) {
        if ( in != null && !in.isEmpty() ) {
            return in.get( 0 );
        }
        else {
            return null;
        }
    }

    @LastElement
    public <T> T last( List<T> in ) {
        if ( in != null && !in.isEmpty() ) {
            return in.get( in.size() - 1 );
        }
        else {
            return null;
        }
    }
}
public class Source {
    private List<Integer> myIntegers;
    private List<String> myStrings;
}
public class Target {
    private Integer myInteger;
    private String myString;
}
// uses = IterableNonInterableUtil.class 引入對象方式
@Mapper( uses = IterableNonInterableUtil.class )
public interface SourceTargetMapper {

    SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );

    // qualifiedBy = FirstElement.class  指定具體方法
    @Mapping( source = "myIntegers", target = "myInteger", qualifiedBy = FirstElement.class )
    @Mapping( source = "myStrings", target = "myString", qualifiedBy = LastElement.class )
    Target toTarget( Source s );
}

public class Main {
    public static void main( String[] args ) {
        Source s = new Source();
        s.setMyIntegers( Arrays.asList( 5, 3, 7 ) );
        s.setMyStrings( Arrays.asList( "five", "three", "seven " ) );

        Target t = SourceTargetMapper.MAPPER.toTarget( s );
        System.out.println( t.getMyInteger() ); // 5
        System.out.println( t.getMyString() ); // seven
    }
}

2. 反向注入 @InheritInverseConfiguration

@InheritInverseConfiguration 會根據正向轉換生成對應的反向Mapping字段映射。

實體類(省略 get、set)
public class Customer {
    private Long id;
    private String name;
    private Collection<OrderItem> orderItems;
}
public class CustomerDto {
    public Long id;
    public String customerName;
    public List<OrderItemDto> orders;
}
public class OrderItem {
    private String name;
    private Long quantity;
}
public class OrderItemDto {
    public String name;
    public Long quantity;
}
@Mapper(uses = { OrderItemMapper.class })
public interface CustomerMapper {

    CustomerMapper MAPPER = Mappers.getMapper( CustomerMapper.class );

    @Mapping(source = "orders", target = "orderItems")
    @Mapping(source = "customerName", target = "name")
    Customer toCustomer(CustomerDto customerDto);

    @InheritInverseConfiguration // 這里相當於正向 toCustomer 方法的 Mapping,等價於注釋的Mapping
    // @Mapping(source = "orderItems", target = "orders")
    // @Mapping(source = "name", target = "customerName")
    CustomerDto fromCustomer(Customer customer);
}
@Mapper
public interface OrderItemMapper {

    OrderItemMapper MAPPER = Mappers.getMapper(OrderItemMapper.class);

    OrderItem toOrder(OrderItemDto orderItemDto);

    @InheritInverseConfiguration // 當正向沒有不同時,可以忽略不寫
    OrderItemDto fromOrder(OrderItem orderItem);
}

3. 克隆 clone ( 1.4 可用)

    /**
     * Allows detailed control over the mapping process.
     *
     * @return the mapping control
     *
     * @since 1.4
     *
     * @see org.mapstruct.control.DeepClone
     * @see org.mapstruct.control.NoComplexMapping
     * @see org.mapstruct.control.MappingControl
     */
    Class<? extends Annotation> mappingControl() default MappingControl.class;
實體類(省略 get、set)
public class CustomerDto {
    private Long id;
    private String customerName;
    private List<OrderItemDto> orders;
    private Map<OrderItemKeyDto, OrderItemDto> stock;
}
public class OrderItemDto {
    private String name;
    private Long quantity;
}
public class OrderItemKeyDto {
    private long stockNumber;
}
@Mapper(mappingControl = DeepClone.class)
public interface Cloner {

    Cloner MAPPER = Mappers.getMapper( Cloner.class );

    CustomerDto clone(CustomerDto customerDto);
}
調用測試
public class ClonerTest {

    @Test
    public void testMapDtoToEntity() {

        CustomerDto customerDto = new CustomerDto();
        customerDto.setId( 10L );
        customerDto.setCustomerName("Jaques" );
        OrderItemDto order1 = new OrderItemDto();
        order1.setName ("Table" );
        order1.setQuantity( 2L );
        customerDto.setOrders( new ArrayList<>( Collections.singleton( order1 ) ) );
        OrderItemKeyDto key = new OrderItemKeyDto();
        key.setStockNumber( 5 );
        Map stock = new HashMap(  );
        stock.put( key, order1 );
        customerDto.setStock( stock );

        CustomerDto customer = Cloner.MAPPER.clone( customerDto );

        System.out.println(customer); // org.mapstruct.example.dto.CustomerDto@504bae78
        System.out.println(customerDto); // org.mapstruct.example.dto.CustomerDto@3b764bce

        // check if cloned
        assertThat( customer.getId() ).isEqualTo( 10 );
        assertThat( customer.getCustomerName() ).isEqualTo( "Jaques" );
        assertThat( customer.getOrders() )
            .extracting( "name", "quantity" )
            .containsExactly( tuple( "Table", 2L ) );
        assertThat( customer.getStock()  ).isNotNull();
        assertThat( customer.getStock() ).hasSize( 1 );

        Map.Entry<OrderItemKeyDto, OrderItemDto> entry = customer.getStock().entrySet().iterator().next();
        assertThat( entry.getKey().getStockNumber() ).isEqualTo( 5 );
        assertThat( entry.getValue().getName() ).isEqualTo( "Table" );
        assertThat( entry.getValue().getQuantity() ).isEqualTo( 2L );

        // check mapper really created new objects
        assertThat( customer ).isNotSameAs( customerDto );
        assertThat( customer.getOrders().get( 0 ) ).isNotEqualTo( order1 );
        assertThat( entry.getKey() ).isNotEqualTo( key );
        assertThat( entry.getValue() ).isNotEqualTo( order1 );
        assertThat( entry.getValue() ).isNotEqualTo( customer.getOrders().get( 0 ) );
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM