在Java 项目开发中,存在需要连个不同类的对象的转化的情况,
例如VO与DO 的中同一逻辑对象的值转换。
@AllArgsConstructor
@Data
public class UserVo {
private Long id;
private String username;
private String password;
private String phoneNum;
private String email;
private Role role;
}
public class UserDo {
private Long id;
private String username;
private String password;
private String phoneNum;
private String email;
private Role role;
}
两个类中的属性大致相同,但是应为所属的逻辑分层,导致被分成不同的POJO对象,对于业务分层交互中会用到两个对象的值的交换。
通常的解决方案:
自行书写转换类,进行值转换
使用Java内省机制,Apache/Spring 的BeanUtil.copyProperties
MapStruct
Order.java
package com.liaojl.test.mapstruct;
public class Order {
/**
* 订单id
*/
private Long id;
/**
* 订单编号
*/
private String orderSn;
/**
* 收货人姓名/号码
*/
private String receiverKeyword;
/**
* 订单状态:0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单
*/
private Integer status;
/**
* 订单类型:0->正常订单;1->秒杀订单
*/
private Integer orderType;
/**
* 订单来源:0->PC订单;1->app订单
*/
private Integer sourceType;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOrderSn() {
return orderSn;
}
public void setOrderSn(String orderSn) {
this.orderSn = orderSn;
}
public String getReceiverKeyword() {
return receiverKeyword;
}
public void setReceiverKeyword(String receiverKeyword) {
this.receiverKeyword = receiverKeyword;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getOrderType() {
return orderType;
}
public void setOrderType(Integer orderType) {
this.orderType = orderType;
}
public Integer getSourceType() {
return sourceType;
}
public void setSourceType(Integer sourceType) {
this.sourceType = sourceType;
}
}
OrderQueryParam.java
package com.liaojl.test.mapstruct;
public class OrderQueryParam {
/**
* 订单编号
*/
private String orderSn;
/**
* 收货人姓名/号码
*/
private String receiverKeyword;
/**
* 订单状态:0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单
*/
private Integer status;
/**
* 订单类型:0->正常订单;1->秒杀订单
*/
private Integer orderType;
/**
* 订单来源:0->PC订单;1->app订单
*/
private Integer sourceType;
public String getOrderSn() {
return orderSn;
}
public void setOrderSn(String orderSn) {
this.orderSn = orderSn;
}
public String getReceiverKeyword() {
return receiverKeyword;
}
public void setReceiverKeyword(String receiverKeyword) {
this.receiverKeyword = receiverKeyword;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getOrderType() {
return orderType;
}
public void setOrderType(Integer orderType) {
this.orderType = orderType;
}
public Integer getSourceType() {
return sourceType;
}
public void setSourceType(Integer sourceType) {
this.sourceType = sourceType;
}
}
配置转换类
package com.liaojl.test.mapstruct.mapper;
import com.liaojl.test.mapstruct.Order;
import com.liaojl.test.mapstruct.OrderQueryParam;
import org.mapstruct.Mapper;
@Mapper
public interface OrderMapper {
OrderQueryParam entity2queryParam(Order order);
}
测试
/**
* @author: liaojl
* @date: 2020/5/30 18:09
* @since: 1.0
*/
public class Test {
@org.junit.Test
public void entity2queryParam() {
Order order = new Order();
order.setId(12345L);
order.setOrderSn("orderSn");
order.setOrderType(0);
order.setReceiverKeyword("keyword");
order.setSourceType(1);
order.setStatus(2);
OrderMapper mapper = Mappers.getMapper(OrderMapper.class);
OrderQueryParam orderQueryParam = mapper.entity2queryParam(order);
assertEquals(orderQueryParam.getOrderSn(), order.getOrderSn());
assertEquals(orderQueryParam.getOrderType(), order.getOrderType());
assertEquals(orderQueryParam.getReceiverKeyword(), order.getReceiverKeyword());
assertEquals(orderQueryParam.getSourceType(), order.getSourceType());
assertEquals(orderQueryParam.getStatus(), order.getStatus());
}
}
package com.liaojl.test.mapstruct.mapper;
import com.liaojl.test.mapstruct.Order;
import com.liaojl.test.mapstruct.OrderQueryParam;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-05-30T23:20:57+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 11.0.6 (Amazon.com Inc.)"
)
public class OrderMapperImpl implements OrderMapper {
@Override
public OrderQueryParam entity2queryParam(Order order) {
if ( order == null ) {
return null;
}
OrderQueryParam orderQueryParam = new OrderQueryParam();
orderQueryParam.setOrderSn( order.getOrderSn() );
orderQueryParam.setReceiverKeyword( order.getReceiverKeyword() );
orderQueryParam.setStatus( order.getStatus() );
orderQueryParam.setOrderType( order.getOrderType() );
orderQueryParam.setSourceType( order.getSourceType() );
return orderQueryParam;
}
}
基于Java Processororg.mapstruct.ap.MappingProcessor
@org.mapstruct.Mapping注解用来声明成员属性的映射。该注解有两个重要的属性:
当两者属性相同时可以忽略,MapStruct 转换时终调用的是 target的setter 和 source 的 getter 方法,而非反射。这也是其性能比较好的原因之一。
@Mapper
public interface OrderMapper {
OrderQueryParam entity2queryParam(Order order);
}
针对 target与source 的属性名称 存在差异性时,可以进行自定义转换
@Mapping(target = "a", source = "B.a")
@Mapping(target = "b", source = "b1")
Target sourceToTarget(Source source);
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
格式化也是我们经常使用的操作,比如数字格式化,日期格式化。
这是处理数字格式化的操作,遵循java.text.DecimalFormat的规范:
@Mapping(source = "price", numberFormat = "$#.00")
下面展示了将一个日期集合映射到日期字符串集合的格式化操作上:
@IterableMapping(dateFormat = "dd.MM.yyyy")
List stringListToDateList(List dates);
下面演示如何使用LocalDateTime 作为当前的时间值注入 addTime 属性中。
首先在@org.mapstruct.Mapper 的 imports 属性中导入 LocalDateTime,该属性是数组意味着你可以根据需要导入更多的处理类:
@Mapper(imports = {LocalDateTime.class})
接下来只需要在对应的方法上添加注解@org.mapstruct.Mapping ,其属性expression 接收一个 java() 包括的表达式:
@Mapping(target = "addTime", expression = "java(LocalDateTime.now())")
@Mapping(target = "addTime", expression = "java(LocalDateTime.parse(car.manufactureDateStr))")
CarDTO carToCarDTO(Car car);
配置多个bean属性的映射。
提示:使用Java 8或更高版本时,可以省略@Mappings 包装器注释,并直接在一个方法上指定多个@Mapping注释。
这两个示例是相等的。
// before Java 8
@Mapper
public interface MyMapper {
@Mappings({
@Mapping(source = "first", target = "firstProperty"),
@Mapping(source = "second", target = "secondProperty")
})
HumanDto toHumanDto(Human human);
}
// Java 8 and later
@Mapper
public interface MyMapper {
@Mapping(source = "first", target = "firstProperty"),
@Mapping(source = "second", target = "secondProperty")
HumanDto toHumanDto(Human human);
}
MapStruct 转换 Mapper 注入Spring IoC 容器
如果使用要把Mapper 注入Spring IoC 容器我们只需要这么声明,不用再构建一个单例,就可以像其他 spring bean一样对注解的Bean 进行引用了:
@Mapper(componentModel = "spring")
public interface OrderMapper {
OrderQueryParam entity2queryParam(Order order);
}