在一次查询中保存瞬态对象的整个一对多结构

Saving an entire one-to-many structure of transient objects in one query

简而言之

我似乎已经多次使用一种主要的反模式来保存对象。我已经通读了有限的 Objectify 文档,但似乎找不到要使用的正确模式。

详情

我有多个要存储的对象。它们都是暂时的(它们还不存在于数据库中)并且它们具有一对多的关系。我不想在层次结构中的每个最后一个对象上坐下来调用 ofy().save()。

在下面的示例中,玩家有一个纸牌列表。

我的模特:

@Entity
public class Player {
  @Id private Long id = null;//will be generated

  private List<Ref<Card>> cards = new ArrayList<Ref<Card>>();
  //getters and setters here
}

public class Card{
  @Id private Long id = null;//will be generated

  //lots of other fields and getters and setters here
}

我的操作:

我需要创建一个新玩家和新卡片,玩家在他的列表中引用卡片 "cards."

理想的解决方案:

我只想创建玩家和卡片 java 对象,设置它们的关系,然后将它们传递给 Objectify 进行保存。像这样:

Player player = new Player();
Card card = new Card();
player.setPlayer(Ref.create(card));
ofy.save().entity(player).now();

那将失败。第 3 行尝试为 Card 创建一个新的 Ref,这无法完成,因为 Card 还没有 Id,一旦它已经持久化就会分配给它。看来我永远不能将一个对象与另一个对象关联起来,直到一个对象已经被保存。

当前糟糕的解决方案

所以,我的解决办法肯定是先保存Card,然后关联到Player,再保存player。

Player player = new Player();
Card card = new Card();
ofy().save().entity(card).now();
player.setPlayer(Ref.create(card));
ofy().save().entity(card).now();

这太疯狂了。起初这似乎是合理的,但我的应用程序处理的关系远不止于此,并且使用这种模式,我的算法将是一个蜘蛛网,用于在保存我实际关心的实体之前检查集合中的瞬态对象。

必须有某种方式告诉 Objectify 只保存所有 child/related 实体以及我请求的实体,并且进一步生成必要的 ID 而不是向我抛出异常。

此外,我还需要这种 "recursive save" 解决方案,即使我的 none 对象是瞬态的(即它们都已经有 ID)。我不能浪费我的时间遍历集合,然后遍历这些集合中的所有集合并保存它们。我需要一些方法来告诉 Objectify 只保存我刚刚传递给你的整个对象继承关系。

我一直在阅读这个@Load 注释,我觉得可能其中缺少某些东西...我不知道。需要帮忙。文档很薄。

更新的解决方案 为了后代—— 使用 allocateId() 方法将整个 ID 生成约束从数据库中解耦出来,你会得到一个非常干净的模式,特别是如果你像我那样做的话: 所有数据库@Entity 类 都有一个私有构造函数和一个用于创建临时对象的静态public 工厂。这个静态工厂方法 ( createTransient() ) 总是会分配一个新的 ID。那么,所有客户端代码都可以使用此方法来获取新的瞬态对象,或者使用明显的对象化负载来获取现有的持久化实例。简单的。完毕。可爱

我推荐两件事:

  1. 使用 ObjectifyFactory.allocateId() 构建对象时手动分配 ID。不要使用 "save with null autogenerates" 功能。正如您所注意到的,处理具有空 ID 的实体对象是一个 PITA,因此不允许它们存在。
  2. 使用延迟保存。 ofy().defer().save().entity(blah); 您可以通过这种方式保存几乎任意数量的内容,并且它们只会在提交(或关闭 objectify 会话)时保存一次。多次延迟对同一实体的保存只会产生一次保存。

这种将 id 保留为空并在保存时填充它的模式是 JPA 时代的遗留物。它也不能很好地与 JPA 一起工作;有很多令人沮丧的边缘案例处理缺少 id 的实体(尤其是当你想把它们放在地图或集合中时)。最好的解决方案是简单地保证没有实体首先会丢失 id。

请注意,您需要在自定义构造函数中分配 id, 而不是 Objectify 用于在加载时构建实体的无参数构造函数。分配一个 id 很便宜,但仍然是对 GAE 服务层的调用,您不想在每次加载时都这样做。