目录
2.3.2、属性名不相同, 可通过 @Mapping 注解进行指定转化
1、MapStruct是什么
MapStruct 是一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。
实际编码中会涉及到多种对象,po、vo、dto、entity、do、domain这些定义的对象运用在不同的场景模块中,这种对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具。
以前是通过反射的方法实现,但是现在无论是 BeanUtils, BeanCopier 等在使用反射的时候都会影响到性能,再后来自己写转换器一个个赋值,但是很浪费时间, 而且在添加新的字段的时候也要进行方法的修改。
MapStruct的目标是通过尽可能地自动化来简化这项工作。
与其他映射框架不同,MapStruct在编译时生成bean映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。
2、MapStruct的使用
2.1、引入依赖
<properties>
<!--项目编码格式-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--java版本号-->
<java.version>1.8</java.version>
<!--mapstruct对象属性映射框架-->
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
<!--对象属性简化框架-->
<org.projectlombok.version>1.18.16</org.projectlombok.version>
</properties>
<dependencies>
<!--包含一些注解-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!--注解处理器,根据注解自动生成mapper的实现-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
2.2、使用方式
2.2.1、工厂类方式
@Mapper
public interface OrderItemMapper {
OrderItemMapper MAPPER = Mappers.getMapper(OrderItemMapper.class);
/**
* OrderItemDto 转为 OrderItem
*
* @param orderItemDto 订单DTO对象
* @return OrderItem
* @author kaifeng
* @date 2021/2/21 5:06 下午
*/
OrderItem dto2OrderItem(OrderItemDto orderItemDto);
}
使用方式,调用工厂类:
OrderItemMapper.MAPPER.dto2OrderItem(new OrderItemDto());
2.2.2、Spring 注入方式
@Mapper(componentModel = "spring")
public interface OrderItemMapper {
// OrderItemMapper MAPPER = Mappers.getMapper(OrderItemMapper.class);
/**
* OrderItemDto 转为 OrderItem
*
* @param orderItemDto 订单DTO对象
* @return OrderItem
* @author kaifeng
* @date 2021/2/21 5:06 下午
*/
OrderItem dto2OrderItem(OrderItemDto orderItemDto);
}
使用方式,对象注入:
@Autowired
private OrderItemMapper orderItemMapper;
2.3、转换方式
2.3.1、属性名称相同,直接转换
类似于BeanUtils转换的方式,按照属性名称匹配进行赋值
/**
* 产品信息对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:10 上午
*/
@Data
public class Product {
private String productId;
private String name;
}
/**
* 产品信息DTO对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:11 上午
*/
@Data
public class ProductDTO implements Serializable {
private String productId;
private String name;
}
/**
* 产品对象转换
*
* @author : liukaifeng
* @date : 2021/2/28 10:12 上午
*/
public interface ProductMapper {
ProductDTO toDTO(Product product);
}
2.3.2、属性名不相同, 可通过 @Mapping 注解进行指定转化
/**
* 产品信息对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:10 上午
*/
@Data
public class Product {
private String productId;
private String name;
}
/**
* 产品信息DTO对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:11 上午
*/
@Data
public class ProductDTO implements Serializable {
private String productId;
private String productName;
}
/**
* 产品对象转换
*
* @author : liukaifeng
* @date : 2021/2/28 10:12 上午
*/
public interface ProductMapper {
@Mapping(source = "name", target = "productName")
ProductDTO toDTO(Product product);
}
2.3.3、Mapper 中使用自定义的转换
对于某些类型, 无法按照属性名称直接转换,就需要自定义的方法来进行转换。
2.3.3.1、日期处理
/**
* 产品信息对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:10 上午
*/
@Data
public class Product {
private String productId;
private String name;
private BigDecimal price;
/**
* 生产时间
*/
private Date productionTime;
/**
* 过期时间
*/
private String expirationTime;
}
/**
* 产品信息DTO对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:11 上午
*/
@Data
public class ProductDTO implements Serializable {
/**
* 产品id
*/
private String productId;
/**
* 产品名称
*/
private String productName;
/**
* 产品规格
*/
private String specs;
/**
* 产品描述
*/
private String describe;
/**
* 产品价格
*/
private String price;
/**
* 生产时间
*/
private String productionTime;
/**
* 过期时间
*/
private Date expirationTime;
}
@Test
public void test2DTODate() {
Product product = new Product();
product.setProductId("1");
product.setName("小汽车");
product.setProductionTime(new Date());
product.setExpirationTime("2021-02-28 11:22:30");
ProductDTO productDTO = ProductMapper.MAPPER.toDTODate(product);
System.out.println(productDTO);
}
2.3.3.2、金额处理
/**
* 产品信息对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:10 上午
*/
@Data
public class Product {
private String productId;
private String name;
private BigDecimal price;
}
/**
* 产品信息DTO对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:11 上午
*/
@Data
public class ProductDTO implements Serializable {
/**
* 产品id
*/
private String productId;
/**
* 产品名称
*/
private String productName;
/**
* 产品规格
*/
private String specs;
/**
* 产品描述
*/
private String describe;
/**
* 产品价格
*/
private String price;
}
/**
* 产品对象转换
*
* @author : liukaifeng
* @date : 2021/2/28 10:12 上午
*/
//@Mapper(componentModel = "spring")
public interface ProductMapper {
ProductMapper MAPPER = Mappers.getMapper(ProductMapper.class);
/**
* BigDecimal转换成字符串
*
* @param product 产品基础信息对象
* @return : ProductDTO
* @author : liukaifeng
* @date : 2021/2/28 10:25 上午
*/
@Mapping(target = "productId", source = "product.productId")
@Mapping(source = "name", target = "productName")
@Mapping(target = "price", numberFormat = "#.00元")
ProductDTO toPriceDTO(Product product);
}
2.3.3.3、自定义转换器
MapStruct支持自定义转换器,实现类型之间自定义规则的转换。
一个自定义映射器可以定义多个映射方法,匹配时,是以方法的入参和出参进行匹配。如果绑定的映射中,存在多个相同的入参和出参方法,将会报错。
如果多个入参或者出参方法存在继承关系,将会匹配最具体的那一个方法。
/**
* 产品对象
* 用于自定义转换器测试
* @author : liukaifeng
* @date : 2021/2/28 12:04 下午
*/
@Data
public class CProduct {
private String productId;
private List<String> images;
}
/**
* 产品DTO对象
* 用于自定义转换器测试
*
* @author : liukaifeng
* @date : 2021/2/28 12:04 下午
*/
@Data
public class CProductDTO implements Serializable {
private static final long serialVersionUID = 2184784038009791692L;
private String productId;
private String images;
}
/**
* 图片对象转换器
*
* @author : liukaifeng
* @date : 2021/2/28 12:06 下午
*/
public class ImageFormater {
public String format(List<String> images) {
return String.join(",", images);
}
}
/**
* 自定义转换器验证
*
* @author : liukaifeng
* @date : 2021/2/28 11:45 上午
*/
//@Mapper(componentModel = "spring", uses = ImageFormater.class)
@Mapper(uses = ImageFormater.class)
public interface CProductMapper {
CProductMapper MAPPER = Mappers.getMapper(CProductMapper.class);
CProductDTO toDTO(CProduct product);
}
2.4、多个对象转一个对象
/**
* 产品信息对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:10 上午
*/
@Data
public class Product {
private String productId;
private String name;
}
/**
* 产品详情
*
* @author : liukaifeng
* @date : 2021/2/28 10:21 上午
*/
@Data
public class ProductDetail {
/**
* 产品规格
*/
private String specs;
/**
* 产品描述
*/
private String describe;
}
/**
* 产品信息DTO对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:11 上午
*/
@Data
public class ProductDTO implements Serializable {
/**
* 产品id
*/
private String productId;
/**
* 产品名称
*/
private String productName;
/**
* 产品规格
*/
private String specs;
/**
* 产品描述
*/
private String describe;
}
/**
* 产品对象转换
*
* @author : liukaifeng
* @date : 2021/2/28 10:12 上午
*/
//@Mapper(componentModel = "spring")
public interface ProductMapper {
ProductMapper MAPPER = Mappers.getMapper(ProductMapper.class);
/**
* 转为产品DTO
* 个别字段名称不一致,使用 @Mapping 指定映射关系
*
* @param product 产品基础信息对象
* @return : ProductDTO
* @author : liukaifeng
* @date : 2021/2/28 10:25 上午
*/
@Mapping(source = "name", target = "productName")
ProductDTO toDTO(Product product);
/**
* 多对象转为一个产品DTO对象
*
* @param product 产品基础信息对象
* @return : ProductDTO
* @author : liukaifeng
* @date : 2021/2/28 10:25 上午
*/
@Mapping(target = "productId", source = "product.productId")
@Mapping(source = "name", target = "productName")
@Mapping(target = "specs", source = "detail.specs")
@Mapping(target = "desc", source = "detail.desc")
ProductDTO toDetailDTO(Product product, ProductDetail detail);
}
2.5、多层对象转换(嵌套对象)
/**
* 产品对象,内嵌产品详情对象
*
* @author : liukaifeng
* @date : 2021/2/28 11:40 上午
*/
@Data
public class NestProduct {
private String productId;
private NestProductDetail nestProductDetail;
}
/**
* 产品详情对象
*
* @author : liukaifeng
* @date : 2021/2/28 11:41 上午
*/
@Data
public class NestProductDetail {
private String id;
}
/**
* 产品DTO对象
*
* @author : liukaifeng
* @date : 2021/2/28 11:42 上午
*/
@Data
public class NestProductDTO implements Serializable {
private static final long serialVersionUID = 2184784038009791692L;
private String productId;
private NestProductDetailDTO nestProductDetailDTO;
}
/**
* 产品DTO详情对象
*
* @author : liukaifeng
* @date : 2021/2/28 11:43 上午
*/
@Data
public class NestProductDetailDTO implements Serializable {
private String productId;
private String detailId;
}
@Test
public void test2NestProductDTO() {
NestProductDetail nestProductDetail = new NestProductDetail();
nestProductDetail.setId("100");
NestProduct product = new NestProduct();
product.setProductId("1");
product.setNestProductDetail(nestProductDetail);
NestProductDTO nestProductDTO = NestProductMapper.MAPPER.toDTO(product);
System.out.println(nestProductDTO);
}
2.6、缺省值(默认值)和常量
MapStruct允许设置缺省值和常量,同时缺省值允许使用表达式。
注意:使用缺省值,源字段必须存在,否则缺省值不生效,否则应该使用常量。
/**
* 产品信息对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:10 上午
*/
@Data
public class Product {
private String productId;
private String name;
private BigDecimal price;
/**
* 生产时间
*/
private Date productionTime;
/**
* 过期时间
*/
private String expirationTime;
/**
* 随机数
*/
private String random;
}
/**
* 产品信息DTO对象
*
* @author : liukaifeng
* @date : 2021/2/28 10:11 上午
*/
@Data
public class ProductDTO implements Serializable {
/**
* 产品id
*/
private String productId;
/**
* 产品名称
*/
private String productName;
/**
* 产品规格
*/
private String specs;
/**
* 产品描述
*/
private String describe;
/**
* 产品价格
*/
private String price;
/**
* 生产时间
*/
private String productionTime;
/**
* 过期时间
*/
private Date expirationTime;
/**
* 随机数
*/
private String random;
}
/**
* 缺省值验证
*
* @author : liukaifeng
* @date : 2021/2/28 2:12 下午
*/
//@Mapper(componentModel = "spring", imports = UUID.class)
@Mapper(imports = UUID.class)
public interface DefaultValueMapper {
DefaultValueMapper MAPPER = Mappers.getMapper(DefaultValueMapper.class);
@Mapping(target = "productId", source = "productId", defaultValue = "0") //当product的productId为null,设置为0
@Mapping(target = "random", source = "random", defaultExpression = "java(UUID.randomUUID().toString())") //缺省设置随机数
@Mapping(target = "productionTime", dateFormat = "yyyy-MM-dd", constant = "2021-02-28") //默认设置为2021-02-28
ProductDTO toDTO(Product product);
}
@Test
public void testProductDTODefaultValue() {
ProductDTO productDTO = DefaultValueMapper.MAPPER.toDTO(new Product());
System.out.printf(productDTO.toString());
}
2.7、使用java表达式进行映射
@Mapper(imports = DecimalUtils.class) //导入java表达式使用的类
public interface Demo16Assembler {
@Mapping(target = "price", expression = "java(product.getPrice1() + product.getPrice2())") //字段相加
@Mapping(target = "price2", expression = "java(DecimalUtils.add(product.getPrice1(), product.getPrice2()))")//使用工具类处理
ProductDTO toDTO(Product product);
}
3、MapStruct的注解说明
@Mapper 只有在接口加上这个注解, MapStruct 才会去实现该接口
@Mapper 里有个 componentModel 属性,主要是指定实现类的类型,有如下4种方式
default: 这是默认的情况,MapStruct不使用任何组件类型, 可以通过Mappers.getMapper(Class)方式获取自动生成的实例对象。
cdi: the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject。
spring: 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入。
jsr330: 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取。
@Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
source:源属性
target:目标属性
dateFormat:String 到 Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat 的日期格式
numberFormat:数值格式化, 例:"0.00"
expression: 自定义java代码实现属性映射
ignore: 忽略这个字段
@Mappings:配置多个@Mapping
@MappingTarget 用于更新已有对象
@InheritConfiguration 用于继承配置
4、MapStruct 相关资料
1、MapStruct 1.3.0.Final参考指南
2、5种常见Bean映射工具的性能比对
https://www.baeldung.com/java-performance-mapping-frameworks
3、官方文档:
https://mapstruct.org/documentation/stable/reference/html/
4、官方FAQ:
5、官方Example: