【问题标题】:How to deal with transient entities after deserialization反序列化后如何处理瞬态实体
【发布时间】:2021-08-10 23:21:52
【问题描述】:

假设我有一个带有控制器、服务和数据层的简单 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");
     }

}

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

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

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

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

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

(样本)

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

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

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

【问题讨论】:

    标签: java spring-boot hibernate objectmapper modelmapper


    【解决方案1】:

    据我了解,modelMapper 很难将 DTO 中的 id 映射到实际实体。 问题是 modelMapper 或某些服务必须进行查找并注入实体。

    1. 如果类别是有限集,是否可以使用 ENUM 并使用静态 ENUM 映射?

    2. 可以将逻辑切换为读取 if(listOfCategoriesToAvoid.contains(item.getCategory())){ throw new IllegalArgumentException("Items with category 5 are not currently permitted"); } 并且您可以填充 listOfCategoriesToAvoid 小查询,甚至可以将其存储在可以是 CSV 的属性文件/表中?

    3. 当您调用 service.save(item) 时,它是否仍然无法填充类别,因为它不会被填充?也许您可以将类别作为 CategoryDTO 发送到 itemDTO 中,该 itemDTO 在 model.map() 调用中填充了 Category 实体。

    不确定这些是否适合您。

    【讨论】:

    • 遗憾的是,这些不是有限集。关于#3,我们过去常常这样做,但它使使用 API 成为一场噩梦。我添加的验证使它看起来像 Category 是一个有限集,但事实并非如此。该特定示例在我们的实际应用程序中不存在。我想我应该找一个更好的。
    【解决方案2】:

    据我所知,map(dto, Item.class) 方法的作用如下:

    Long categoryId = itemDto.getCategoryId();
    Category cat = new Category();
    cat.setId(categoryId);
    outItem.setCategory(cat);
    

    最简单的解决方案是让它在内部执行此操作:

    Long categoryId = itemDto.getCategoryId();
    Category cat = categoryRepo.getById(categoryId);
    outItem.setCategory(cat);
    

    另一种选择是,由于您在完成之前对类别代码 5 进行硬编码,因此您可以硬编码拥有它的类别 ID,如果您不希望用户更改这些 ID。

    【讨论】:

    • 谢谢,但这正是我上面提供的列表中的第一个解决方案。它给我们带来了巨大的性能问题。
    【解决方案3】:

    为什么不直接使用code 作为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 integration 如下所示:

    @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...
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-18
      • 2011-06-19
      • 2014-10-05
      • 1970-01-01
      • 2011-05-06
      • 2012-06-28
      • 2021-04-22
      • 1970-01-01
      相关资源
      最近更新 更多