Spring 数据 JPA getById 与 GetReferenceById

问题描述 投票:0回答:3

有谁知道为什么

getById
getReferenceById
的工作方式不同? 我正在使用
JpaRepository
并且方法
getReferenceById
不会抛出
EntityNotFoundException
但 getById 在对象不存在时会抛出此异常

java spring-boot jpa
3个回答
3
投票

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


2
投票

有时,仅使用

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 应用程序。


0
投票

你所说的似乎有点奇怪,因为已弃用的

org.springframework.data.jpa.repository.JpaRepository#getById
的实现只是委托给它的替代品,
org.springframework.data.jpa.repository.JpaRepository#getReferenceById

正如您在该方法的实现中所看到的 (

org.springframework.data.jpa.repository.support.SimpleJpaRepository#getReferenceById
),它直接使用
EntityManager#getReference
方法。
当使用 Hibernate 时,这通常只会创建一个代理,并且当您访问其中一个字段时,代理会从数据库中获取实际值 - 或者抛出一个
EntityNotFoundException
,以防它不存在。

是否您更改了代码中的某些内容,或者调试器试图显示方法的

toString
来欺骗您?

© www.soinside.com 2019 - 2024. All rights reserved.