实体与DTO如何转换

下面是一些常用的转换库:

  1. Dozer

    该项目目前不活跃,并且很可能在未来被弃用。

  2. ModelMapper

    一个智能对象映射库,可自动将对象相互映射。它采用基于约定的方法,同时提供简单、重构安全的应用程序接口(API)来处理特定用例。

  3. MapStruct

    它是一个代码生成器,它基于约定优于配置的方法,极大地简化了 Java Bean 类型之间的映射实现。生成的映射代码使用简单的方法调用,因此执行速度快、类型安全且易于理解。

  4. Orika

    是一个 Java Bean 映射框架,它(除其他功能外)可以递归地将数据从一个对象复制到另一个对象。它在开发多层应用程序时非常有用。

  5. Selma

    它一方面是一个注解处理器,能够在编译时生成处理字段到字段映射的 Java 代码;另一方面,它是一个运行时库,用于实例化和调用生成的映射器。

ModelMapper的使用。

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>3.2.1</version>
</dependency>

在Spring环境下,建议配置如下的Bean以方便我们进行转换。

@Configuration
public class ModelMapperConfig {

  @Bean
  public ModelMapper modelMapper() {
    return new ModelMapper() ;
  }
}

如果你不是在Spring环境,那么你可以在使用的时候直接new创建即可。

2.2 准备实体类&DTO

订单实体类

public class Order {
  private String orderNumber ;
  private double orderAmount ;
  private double tax ;
  private Customer customer ;
  private Address shippingAddress ;
  // getter and setter
}
public class Customer {
  private String userId ;
  private String firstName ;
  private String lastName ;
  private String email ;
  // getter and setter
}
public class Address {
  private String addressLine1 ;
  private String street ;
  private String city ;
  private String postalCode ;
  // getter and setter
}

下面是我们希望进行转换的DTO对象。

public class OrderDto { 
   public  String orderNumber ;
   private double orderAmount ;
   private double tax ; 
   private Customer customer ;
   private Address shippingAddress ;
   // getters and setters
}

接下来,我们将围绕上面定义的类进行讲解。

再写个静态的Service


@Service
public class OrderService {

  public Order queryOrder() {
    Order order = new Order() ;
    order.setOrderAmount(266.6D) ;
    order.setTax(1.5D) ;
    order.setOrderNumber("PACK-00001") ;
    
    Customer customer = new Customer("U00001", "Pack", "AKF", "66666@qq.com") ; 
    order.setCustomer(customer) ;
    
    Address address = new Address("XJ0001", "HTYJ", "WLMQ", "830000") ;
    order.setShippingAddress(address) ;
    return order ;
  }
}

该Service用来模拟查询Order对象。

2.3 定义门面Facade

在本示例中,我将使用 Facade 层来简化服务层,如下定义:


@Component
public class OrderFacade {

  private final ModelMapper modelMapper;
  public OrderFacade(ModelMapper modelMapper) {
    this.modelMapper = modelMapper ;
  }

  public OrderDto convert(Order order) {
    return convertToOrderDto(order) ;
  }
  private OrderDto convertToOrderDto(Order order) {
    OrderDto orderDto = this.modelMapper.map(order, OrderDto.class);
    return orderDto;
  }
}

该Facade非常简单就是调用ModelMapper方法进行数据类型的转换。

2.4 定义Controller

@RestController
@RequestMapping("/orders")
public class OrderController {

  private final OrderFacade orderFacade;
  private final OrderService orderService ;

  public OrderController(OrderFacade orderFacade, OrderService orderService) {
    this.orderFacade = orderFacade;
    this.orderService = orderService ;
  }

  @GetMapping(value = "/{id}")
  public ResponseEntity<OrderDto> getOrder(@PathVariable("id") String id) {
    Order order = this.orderService.queryOrder() ;
    OrderDto orderDto = this.orderFacade.convert(order) ;
    return ResponseEntity.status(HttpStatus.OK.value()).body(orderDto) ;
  }
}

3. ModelMapper更多用法

3.1 自定义映射

首先,修改DTO属性如下:


public class OrderDto {
  public String number;
  // ...
}

这里我们希望的是number字段能对应到Order中的orderNumber属性,是否能自动匹配呢?执行如下代码


OrderDto dto = mapper.map(order, OrderDto.class) ;
System.out.println(dto) ;

输出结果

OrderDto2 [number=PACK-00001, orderAmount=266.6, tax=1.5, ...]

能够正确的映射。但是如果两个字段完全没有相似会如何呢?修改DTO如下:


public class OrderDto2 {
  private double money ;
  // ...
}

 我们期望的是该money对应到Order中的orderAmount上,运行上面代码:

OrderDto2 [number=PACK-00001, money=0.0,...]

在这种完全没有相似的情况下,就需要我们自定义映射

ModelMapper mapper = new ModelMapper() ;
mapper.typeMap(Order.class, OrderDto.class).addMappings(mapping -> {
  mapping.map(src -> src.getOrderAmount(), OrderDto::setMoney) ;
}) ;

这里添加了Order到OrderDto转换的映射,将Order中的orderAmount映射到OrderDto中的money。

我们继续修改OrderDto添加如下属性: 我们希望将Customer中的firstName映射到这里的name,可以如下添加映射: 

public class OrderDto {
  private String name ;
  // ...
}

我们希望将Customer中的firstName映射到这里的name,可以如下添加映射:


typeMap.addMapping(
  order -> order.getCustomer().getFirstName(), 
  OrderDto::setName
) ;

 

这就告知在映射时将Customer中的firstName映射到DTO的name属性上。

3.2 跳过属性

如果你希望某些属性不进行映射,你可以如下操作

typeMap.addMappings(mapping -> mapping.skip(OrderDto::setTax));

映射时将忽略DTO中的tax属性。

3.3 属性值转换

转换器允许在映射源属性到目标属性时进行自定义转换,如下示例:


Converter<String, String> toUpperCase = ctx -> ctx.getSource() == null ? null : ctx.getSource().toUpperCase() ;
typeMap.addMappings(
  mapping -> mapping.using(toUpperCase)
    .map(Order::getOrderNumber, OrderDto::setNumber)
) ;

如上我们将Order中的orderNumber值转换为大写后映射到DTO的number属性。

3.4 条件映射

目标属性的映射可以有条件地进行,方法是在映射的同时提供一个条件,如下示例:


Condition<String, String> condition = ctx -> !"Pack-00001".equals(ctx.getSource());
typeMap.addMappings(mapping -> mapping.when(condition).map(Order::getOrderNumber, OrderDto2::setNumber)) ;

这里添加条件,只有Order中的orderNumber属性值不为 "Pack-00001"时才进行映射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值