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);}
}
测试结果