有谁知道为什么
getById
和 getReferenceById
的工作方式不同?
我正在使用 JpaRepository
并且方法 getReferenceById
不会抛出 EntityNotFoundException
但 getById 在对象不存在时会抛出此异常
getReferenceById 返回一个代理并且不执行数据库调用,因此不会调用异常,因为 JPA 不知道具有此 ID 的实体是否存在。
getReferenceById 执行 EntityManager.getReference 并且文档说:
T getReference(java.lang.Class实体类,java.lang.Object 主键)
获取一个实例,其状态可以被延迟获取。如果 数据库中不存在请求的实例, 实例状态为first时抛出EntityNotFoundException 已访问。 (持久化提供者运行时被允许抛出 调用 getReference 时出现 EntityNotFoundException。)应用程序 不应期望实例状态将在 分离,除非实体被应用程序访问 经理开门了。
来源:https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/entitymanager
有时,仅使用
getById
作为获取实体的方式会产生一些重大问题。通常我们更喜欢使用 getReferenceById
方法来节省一些额外的调用,就好像我们使用 getById
这样做时有一个对数据库的额外调用。
假设我们有一个 Category
子实体,它通过 Product
实体中的 Product
引用与父 Category
实体关联。意味着每个产品有多个类别。使用 productId
作为参考向我们的产品添加新类别的代码块如下:
public Category addNewCategory(String categoryName, Long productId) {
Category category = new Category()
.setName(categoryName)
.setProduct(productRepository.findById(productId)
.orElseThrow(
()-> new EntityNotFoundException(
String.format(
"Product with id [%d] was not found!",
productId
)
)
)
);
categoryRepository.save(category);
return category;
}
如果要为产品添加类别,则每次插入新的
findProductById
时,首先需要调用Category
。当您获得 Product
时,请将其插入作为对 Category
实体的引用。
在这种情况下,Spring Data JPA 将生成以下 sql:
SELECT
product0_.id AS id1_0_0_,
product0_.slug AS name2_0_0_,
product0_.title AS title3_0_0_
FROM
product product0_
WHERE
product0_.id = 1
SELECT nextval ('hibernate_sequence')
INSERT INTO category (
product_id,
name,
id
)
VALUES (
1,
'book',
1
)
此查询是由
findById
方法调用生成的,该方法调用的目的是在当前持久性上下文中加载实体。然而,在我们的例子中,我们不需要它。我们只想保存一个新的 Category
实体并将 Product_id 外键列设置为我们已经知道的值。
但是,由于设置底层 Product_id 列值的唯一方法是提供
Product
实体引用,这就是许多开发人员最终调用 findById
方法的原因。
在我们的例子中,运行此 SQL 查询是不必要的,因为我们不需要获取父 Product 实体。但是我们怎样才能摆脱这个额外的 SQL 查询呢? 我们可以使用
getReferenceById
方法来代替。
public PostComment addNewCategory(String name, Long productId) {
Category category = new Category()
.setName(name)
.setProduct(productRepository.getReferenceById(productId));
categoryRepository.save(category);
return category;
}
现在调用相同的
addNewCategory
方法时,我们看到 Product
实体不再被获取,因为它将从 JPA 保存实体代理的缓存位置获取。这样我们就可以优化大量使用数据库的 Spring 应用程序。
你所说的似乎有点奇怪,因为已弃用的
org.springframework.data.jpa.repository.JpaRepository#getById
的实现只是委托给它的替代品,org.springframework.data.jpa.repository.JpaRepository#getReferenceById
。
正如您在该方法的实现中所看到的 (
org.springframework.data.jpa.repository.support.SimpleJpaRepository#getReferenceById
),它直接使用 EntityManager#getReference
方法。EntityNotFoundException
,以防它不存在。
是否您更改了代码中的某些内容,或者调试器试图显示方法的
toString
来欺骗您?