首页 > 解决方案 > 反序列化后如何处理瞬态实体

问题描述

假设我有一个带有控制器、服务和数据层的简单 REST 应用程序。在我的控制器层中,我执行以下操作:

@PostMapping("/items")
void save(ItemDTO dto){

    Item item = map(dto, Item.class);
    service.validate(item);
    service.save(item);
}

但后来我收到错误,因为我的服务层如下所示:

public void validate(Item item) {
    
     if(item.getCategory().getCode().equals(5)){
         throw new IllegalArgumentException("Items with category 5 are not currently permitted");
     }

}

我在 处得到 NullPointerException .equals(5),因为该Item实体是从仅包含 的 DTO 反序列化的category_id,没有其他内容(null除 之外的所有内容id)。

我们已经找到并尝试过的解决方案是:

  1. 制作一个特殊的反序列化器,它接受ids 并自动获取所需的实体。当然,这会导致大量性能问题,类似于将所有关系都标记为FetchType.EAGER.

  2. 让控制器层获取服务层需要的所有实体。问题是,控制器需要知道底层服务是如何工作的,以及它需要什么。

  3. 让服务层在运行任何验证之前验证对象是否需要获取。问题是,我们找不到确定对象是否需要获取的可靠方法。我们最终到处都是这样丑陋的代码:

(样本)

if(item.getCategory().getCode() == null)
    item.setCategory(categoryRepo.findById(item.getCategory().getId()));

您还会采取哪些其他方式来保持服务易于使用?每次我们想要使用相关实体时都必须检查,这真的是违反直觉的。

请注意,这个问题不是要找到解决这个问题的任何方法。更多的是寻找更好的方法来解决它。

标签: javaspring-boothibernateobjectmappermodelmapper

解决方案


你为什么不只是使用codeas 主键Category呢?这样您就不必为这种检查获取任何东西。但根本问题是对象映射器无法处理 JPA 对象的托管性质,即它不知道它应该通过例如 PK 实际检索对象EntityManager#getReference。如果它这样做,那么你不会有问题,因为该方法返回的代理将在第一次调用getCode.

我建议你看看像Blaze-Persistence Entity Views这样的东西,它对类似的东西有一流的支持。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您以您喜欢的方式定义您的目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

@EntityView(Item.class)
// You can omit the strategy to default to QUERY when using the code as PK of Category
@UpdatableEntityView(strategy = FlushStrategy.ENTITY)
public interface ItemDTO {
    @IdMapping
    Long getId();
    String getName();
    void setName(String name);
    CategoryDTO getCategory();
    void setCategory(CategoryDTO category);

    @EntityView(Category.class)
    interface CategoryDTO {
        @IdMapping
        Long getId();
    }
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

ItemDTO a = entityViewManager.find(entityManager, ItemDTO.class, id);

Spring Data 集成允许您几乎像 Spring Data Projections 一样使用它:https ://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<ItemDTO> findAll(Pageable pageable);

最好的部分是,它只会获取实际需要的状态!

在您保存数据的情况下,您可以使用如下所示的Spring WebMvc 集成

@PostMapping("/items")
void save(ItemDTO dto){
    service.save(dto);
}

class ItemService {
  @Autowired
  ItemRepository repository;
  
  @Transactional
  public void save(ItemDTO dto) {
    repository.save(dto);
    Item item = repository.getOne(dto);
    validate(item);
  }

  // other code...
}

推荐阅读