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 {
@Test
public 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 {
@Override
public 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 {
@Autowired
private CarMapper carMapper;
@Test
public 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 {
@Autowired
private CarMapper carMapper;
@Test
public 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 {
@Autowired
private CarMapper carMapper;
@Test
public 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 {
@Autowired
private SourceTargetMapper sourceTargetMapper;
@Test
public 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 {
@Autowired
private OrderMapper orderMapper;
@Test
public void test1() {
System.out.println("测试结果");
OrderType orderType = OrderType.EXTRA;
ExternalOrderType externalOrderType = orderMapper.orderTypeToExternalOrderType(orderType);
System.out.println(externalOrderType);
}
}
测试结果