JPA:如何避免简单地加载对象,以便将其 ID 存储在数据库中?

Posted

技术标签:

【中文标题】JPA:如何避免简单地加载对象,以便将其 ID 存储在数据库中?【英文标题】:JPA: How do I avoid loading an object simply so I can store its ID in the database? 【发布时间】:2016-02-06 12:09:30 【问题描述】:

这道题太简单了,大概看代码就可以了

这是一个非常简单的性能问题。在下面的代码示例中,我希望在我的 Cat 对象上设置 Owner。我有ownerId,但是cats 方法需要Owner 对象,而不是Long。例如:setOwner(Owner owner)

@Autowired OwnerRepository ownerRepository;
@Autowired CatRepository catRepository;

Long ownerId = 21;
Cat cat = new Cat("Jake");
cat.setOwner(ownerRepository.findById(ownerId)); // What a waste of time
catRepository.save(cat)

我正在使用ownerId 加载Owner 对象,因此我可以调用Cat 上的setter,它只是要拉出id,并保存Cat 记录一个owner_id。所以基本上我是在白白地加载一个所有者。

正确的模式是什么?

【问题讨论】:

第一次按 ID 加载 Owner 后没有性能成本。从第二个开始,您将访问 L2 缓存而不是访问数据库 【参考方案1】:

首先,您应该注意加载 Owner 实体的方法。

如果您使用的是 Hibernate Session

// will return the persistent instance and never returns an uninitialized instance
session.get(Owner.class, id);

// might return a proxied instance that is initialized on-demand
session.load(Owner.class, id);

如果您使用的是EntityManager

// will return the persistent instance and never returns an uninitialized instance
em.find(Owner.class, id);

// might return a proxied instance that is initialized on-demand
em.getReference(Owner.class, id);

因此,您应该延迟加载 Owner 实体以避免对缓存或数据库的某些命中。

顺便说一句,我建议您将OwnerCat 之间的关系颠倒过来。

例如:

Owner owner = ownerRepository.load(Owner.class, id);
owner.addCat(myCat);

【讨论】:

我更喜欢猫,所以就领域驱动设计而言,我的猫有统治力。【参考方案2】:

Victor's answer 是正确的(我 +1),但需要通过 EntityManager 或 Hibernate 会话。假设您自动装配的存储库是来自 Spring Data 的 JPA 存储库,并且您更愿意浏览它们,请使用 JpaRepository#getOne 方法。它调用EntityManager#getReference,所以它做同样的事情,向实体返回一个代理。

我认为这里的关系不一定需要颠倒,which mapping to use depends on the situation。在许多情况下,首选多对一。

【讨论】:

【参考方案3】:

可能不是您想要的,但您的问题中没有任何内容暗示您必须使用 JPA 解决这个问题。使用普通的旧 SQL,有些事情要简单得多:

INSERT INTO cat (name, owner_id) VALUES ('Jake', 21)

【讨论】:

【参考方案4】:

如果你使用的是 Hibernate,你可以这样做:

Long ownerId = 21;
Cat cat = new Cat("Jake");
Owner owner = new Owner();
owner.setId(ownerId);
cat.setOwner(owner);
catRepository.save(cat)

它不是标准的 JPA,但是,如果您不愿意迁移到其他 JPA 提供商,从性能角度来看,它是最好的。

更新

正如 Nathan 指出的,您需要确保 Owner 尚未关联(在这种情况下,您可以获得 NonUniqueObjectException,因为持久性上下文最多可以在一级缓存中关联一个实体)。

在这种情况下使用 EntityManager.contains(entity) 没有帮助,因为 Hibernate 将实体存储在 IdentiyHashMap 中,其中键是对象引用本身。

所以你应该使用这个方法,例如,当你有一个用例你必须第一次插入这些实体,或者当你需要更新它们并且Owner没有被加载到当前运行 Persistence Context(直接或通过 JPQL 或 Criteria API)。

否则,请使用EntityManager.getReferemce(Class entityClass, Object primaryKey)。

【讨论】:

@Vlad Mihalcea 如果他使用实体管理器按 ID 检索记录,则在第一次之后它将命中 L2 缓存,因此不会影响性能。 Hibernate默认禁用二级缓存,OP根本没有提及。【参考方案5】:

另一种方式(有时在遗留代码或数据库架构中会派上用场):

@Entity
public class Cat 
  @Column(name = "OWNER_ID")
  private Long ownerId;

  @ManyToOne
  @JoinColumn(name = "OWNER_ID", insertable = false, updatable = false)
  private Owner owner;

【讨论】:

以上是关于JPA:如何避免简单地加载对象,以便将其 ID 存储在数据库中?的主要内容,如果未能解决你的问题,请参考以下文章

jpa如何设置某个字段存到数据库后不能修改,当然整个对象删除除外!

如何将所有数据存储在单个数组中,以便我可以轻松地将其作为请求发布?

Spring Data JPA 原生查询结果实体

如何避免使用 JPA 在实体关系中违反外键约束

如何通过 JPA 获取已保存对象的 id?

JPA 认为我正在删除一个分离的对象