前言
在分层结构的应用程序开发中,为了实现各层之间的解耦,通常会定义不同的数据传输对象(DTO)、视图对象(VO)、业务对象(BO)等,这些对象是从数据库对象派生而来的。在不同层之间传输数据时,我们经常需要进行这些对象之间的相互转换。
此时一般处理两种处理方式:
① 直接使用 Setter 和 Getter 方法转换;
② 使用一些工具类进行转换(e.g. BeanUtil.copyProperties)。
第一种方式如果对象属性比较多时,需要写很多的 Getter/Setter 代码,这种处理过程枯燥且无技术含量的,不仅耗费大量时间而且很容易出错。
第二种方式看起来虽然比第一种方式要简单很多,但是因为其使用了反射,性能不太好,而且需要类型和名称都一样才会进行映射。
解决方案:使用MapStruct
MapStruct 是什么
MapStruct是一个用于Java Bean映射的代码生成器。它可以自动为不同类型的Java对象之间生成映射代码,从而简化开发人员在对象之间进行转换的工作。
MapStruct的主要特点包括:
- 通过注解和接口来定义对象之间的映射关系,使得映射配置更加简洁和灵活。
- 自动生成高效的映射代码,提高映射的性能和可靠性。
- 支持复杂对象之间的映射,包括属性的复制、类型转换、集合映射等。
- 提供了丰富的注解,可以进行属性映射、忽略某些属性、自定义映射逻辑等。
- 支持扩展和自定义转换逻辑,可以通过编写自定义转换器或使用依赖注入来实现更复杂的映射需求。
使用MapStruct可以减少开发人员手动编写大量重复的映射代码的工作量,提高开发效率和代码质量。它在Java开发领域得到了广泛的应用,并且持续得到改进和发展。
MapStruct 使用步骤
引入依赖
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>
入门使用:源对象和目标对象属性一致
创建需要转换的对象
@Data
public class Order {
private Long id;
private Long orderNo;
private Date creatTime;
}
@Data
public class OrderBO {
private Long id;
private Long orderNo;
private Date creatTime;
}
创建转换接口
@Mapper(componentModel = "Spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface OrderBOToOrderConverter {
OrderBO sourceToTarget(Order order);
}
测试方法
@SpringBootTest //获取启动类,加载配置,寻找主配置启动类 (被 @SpringBootApplication 注解的)
@RunWith(SpringRunner.class)
public class mapstructTest {
@Autowired
private OrderBOToOrderConverter orderBOToOrderConverter;
@Test
public void test1(){
Order order = new Order();
order.setId(1L);
order.setOrderNo(11L);
order.setCreatTime(new Date());
OrderBO orderBO = orderBOToOrderConverter.sourceToTarget(order);
System.out.println(orderBO.toString());
}
}
结果
进阶使用
@Mappings是MapStruct注解之一,用于定义多个@Mapping注解的集合。它可以用于Mapper接口中的方法上,用于指定多个字段之间的映射关系。
进阶使用1【基本映射】:源对象与目标对象属性一致,属性名不一致
创建需要转换的对象
@Data
public class OrderDTO {
private Long id;
private Long num;
private Date creatTime;
}
@Data
public class OrderBO {
private Long id;
private Long orderNo;
private Date creatTime;
}
创建转换接口(无映射)
@Mapper(componentModel = "Spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface OrderDTOToOrderBOConverter {
OrderDTO sourceToTarget(OrderBO orderBO);
}
创建转换接口(有映射)
@Mapper(componentModel = "Spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface OrderDTOToOrderBOConverter {
@Mappings({
@Mapping(source = "orderNo", target = "num"),
})
OrderDTO sourceToTarget(OrderBO orderBO);
}
测试方法
@SpringBootTest //获取启动类,加载配置,寻找主配置启动类 (被 @SpringBootApplication 注解的)
@RunWith(SpringRunner.class)
public class mapstructTest {
@Autowired
private OrderDTOToOrderBOConverter orderDTOToOrderBOConverter;
@Test
public void test2(){
OrderBO orderBO = new OrderBO();
orderBO.setId(2L);
orderBO.setOrderNo(22L);
orderBO.setCreatTime(new Date());
OrderDTO dto = orderDTOToOrderBOConverter.sourceToTarget(orderBO);
System.out.println(dto.toString());
}
}
结果(无映射):
结果(有映射):
进阶使用2【表达式映射】:源对象与目标对象属性不一致
创建需要转换的对象
@Data
public class OrderDTO {
private Long id;
private String num;
private Date creatTime;
}
@Data
public class OrderBO {
private Long id;
private Long orderNo;
private Date creatTime;
}
创建转换接口(有映射)
@Mapper(componentModel = "Spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface OrderDTOToOrderBOConverter {
@Mappings({
@Mapping(target = "num", expression = "java(order.getOrderNo().toString())"),
})
OrderDTO sourceToTarget(OrderBO orderBO);
}
测试方法(同进阶使用1)
结果:
进阶使用3【复杂逻辑映射】:源对象与目标对象映射需要经过复杂逻辑处理
创建需要转换的对象
bo中的timestamp与dto中的createTime进行映射,如果时间戳为空字符串,createTime取当前时间
@Data
public class OrderDTO {
private Long id;
private String num;
private Date creatTime;
}
@Data
public class OrderBO {
private Long id;
private Long orderNo;
private String timestamp;
}
创建转换接口(有映射):指定方法处理源参数和目标参数间的逻辑
public final class MapperUtils {
@Named("timestampToDate")
public static Date timestampToDate(String timestamp){
if(!StringUtils.isEmpty(timestamp)){
return new Date(Long.parseLong(timestamp));
}
return new Date();
}
}
@Mapper(componentModel = "Spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR , uses = MapperUtils.class)
public interface OrderDTOToOrderBOConverter {
@Mappings({
@Mapping(target = "num", expression = "java(order.getOrderNo().toString())"),
@Mapping(source = "timestamp", target = "createTime" , qualifiedByName = "timestampToDate")
})
OrderDTO sourceToTarget(OrderBO order);
}
测试方法
@Test
public void test2(){
OrderBO orderBO = new OrderBO();
orderBO.setId(2L);
orderBO.setOrderNo(22L);
orderBO.setTimestamp("1703327395000");
OrderDTO dto = orderDTOToOrderBOConverter.sourceToTarget(orderBO);
System.out.println(dto.toString());
}
结果
性能测试
主要对比MapStruct和BeanUtil.copyProperties之间的性能差异:差了10+倍
MapStruct测试方法
@SpringBootTest //获取启动类,加载配置,寻找主配置启动类 (被 @SpringBootApplication 注解的)
@RunWith(SpringRunner.class)
public class mapstructTest {
@Autowired
private OrderBOToOrderConverter orderBOToOrderConverter;
@Test
public void test1(){
long startTime = System.currentTimeMillis();
for(int i = 0; i < 100000; i++) {
Order order = new Order();
order.setId(1L);
order.setOrderNo(11L);
order.setCreatTime(new Date());
OrderBO orderBO = orderBOToOrderConverter.sourceToTarget(order);
}
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println("运行时间:" + duration + "毫秒");
}
}
结果:
BeanUtil.copyProperties测试方法
@SpringBootTest //获取启动类,加载配置,寻找主配置启动类 (被 @SpringBootApplication 注解的)
@RunWith(SpringRunner.class)
public class mapstructTest {
@Autowired
private OrderBOToOrderConverter orderBOToOrderConverter;
@Test
public void test1(){
long startTime = System.currentTimeMillis();
for(int i = 0; i < 100000; i++) {
Order order = new Order();
order.setId(1L);
order.setOrderNo(11L);
order.setCreatTime(new Date());
OrderBO orderBO = new OrderBO();
BeanUtils.copyProperties(order,orderBO); }
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println("运行时间:" + duration + "毫秒");
}
}
结果:
官方指南:MapStruct