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 实体以避免对缓存或数据库的某些命中。
顺便说一句,我建议您将Owner
和Cat
之间的关系颠倒过来。
例如:
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如何设置某个字段存到数据库后不能修改,当然整个对象删除除外!