小编典典

使用 getOne 和 findOne 方法时 Spring Data JPA

all

我有一个用例,它调用以下内容:

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
    return this.userControlRepository.getOne(id);
}

观察@Transactionalhas Propagation.REQUIRES_NEW 并且存储库使用 getOne
。当我运行该应用程序时,我收到以下错误消息:

Exception in thread "main" org.hibernate.LazyInitializationException: 
could not initialize proxy - no Session
...

但如果我改变所有的getOne(id)工作findOne(id)正常。

顺便说一句,就在用例调用 getUserControlById 方法之前,它已经调用了 insertUserControl 方法

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
    return this.userControlRepository.save(userControl);
}

这两种方法都是 Propagation.REQUIRES_NEW 因为我正在做一个简单的 审计 控制。

我使用该getOne方法是因为它是在JpaRepository接口中定义的,并且我的
Repository 接口从那里扩展,当然我正在使用 JPA。

JpaRepository接口从 CrudRepository扩展 而来。该findOne(id)方法在
中定义CrudRepository

我的问题是:

  1. 为什么方法失败getOne(id)
  2. 我什么时候应该使用该getOne(id)方法?

我正在使用其他存储库,并且都使用该getOne(id)方法并且一切正常,只有当我使用 Propagation.REQUIRES_NEW
时它才会失败。

根据 getOne API:

返回对具有给定标识符的实体的引用。

根据 findOne API:

通过 id 检索实体。

  1. 我什么时候应该使用该findOne(id)方法?

  2. 推荐使用什么方法?


阅读 99

收藏
2022-08-02

共1个答案

小编典典

TL;博士

T findOne(ID id)(旧 API 中的Optional<T> findById(ID id)名称)/(新 API
中的名称)依赖于EntityManager.find()执行 实体预加载

T getOne(ID id)依赖于EntityManager.getReference()执行 实体延迟加载
。所以为了保证实体的有效加载,需要对其调用方法。

findOne()/findById()确实比getOne().
所以在大多数情况下,findOne()/findById()赞成getOne().


API 变更

从至少,2.0版本,Spring-Data-Jpa修改findOne()
以前,它在CrudRepository接口中定义为:

T findOne(ID primaryKey);

现在,findOne()您将找到的唯一方法CrudRepository是在接口中定义QueryByExampleExecutor为:

<S extends T> Optional<S> findOne(Example<S> example);

最终由接口SimpleJpaRepository的默认实现来实现CrudRepository
此方法是通过示例搜索进行的查询,您不希望将其作为替换。

实际上,具有相同行为的方法仍然存在于新 API 中,但方法名称已更改。
它在界面中被重命名findOne()为:findById()CrudRepository

Optional<T> findById(ID id);

现在它返回一个Optional. 这不是那么糟糕,以防止NullPointerException

所以,现在的实际选择是在Optional<T> findById(ID id)和之间T getOne(ID id)


两种不同的方法,它们依赖于两种不同的 JPA EntityManager 检索方法

1)Optional<T> findById(ID id)javadoc声明它:

通过 id 检索实体。

当我们查看实现时,我们可以看到它依赖于EntityManager.find()进行检索:

public Optional<T> findById(ID id) {

    Assert.notNull(id, ID_MUST_NOT_BE_NULL);

    Class<T> domainType = getDomainClass();

    if (metadata == null) {
        return Optional.ofNullable(em.find(domainType, id));
    }

    LockModeType type = metadata.getLockModeType();

    Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

    return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}

em.find()是一个EntityManager声明为的方法:

public <T> T find(Class<T> entityClass, Object primaryKey,
                  Map<String, Object> properties);

它的 javadoc 状态:

通过主键查找,使用指定的属性

因此,检索加载的实体似乎是意料之中的。

2)虽然T getOne(ID id)javadoc状态(重点是我的):

返回对具有给定标识符的实体的 引用。

事实上, 参考 术语实际上是板,JPA API 没有指定任何getOne()方法。
因此,了解 Spring 包装器所做的最好的事情是研究实现:

@Override
public T getOne(ID id) {
    Assert.notNull(id, ID_MUST_NOT_BE_NULL);
    return em.getReference(getDomainClass(), id);
}

em.getReference()是一个EntityManager声明为的方法:

public <T> T getReference(Class<T> entityClass,
                              Object primaryKey);

幸运的是,EntityManagerjavadoc 更好地定义了它的意图(重点是我的):

获取一个实例,其状态可能会被延迟获取 。如果请求的实例在数据库中不存在,则在 第一次访问实例状态时 抛出
EntityNotFoundException 。(当调用 getReference 时,允许持久性提供程序运行时抛出
EntityNotFoundException。) 应用程序不应期望实例状态在 detachment 时可用
,除非它在实体管理器打开时由应用程序访问。

因此,调用getOne()可能会返回一个延迟获取的实体。
这里,惰性获取不是指实体的关系,而是指实体本身。

这意味着如果我们调用getOne()然后 Persistence 上下文关闭,实体可能永远不会加载,因此结果确实是不可预测的。
例如,如果代理对象被序列化,您可以获得null作为序列化结果的引用,或者如果在代理对象上调用方法,LazyInitializationException则会引发异常。
所以在这种情况下,throw是用来处理数据库中不存在的实例EntityNotFoundException的主要原因,因为在实体不存在时可能永远不会执行错误情况。getOne()

在任何情况下,为了确保它的加载,您必须在会话打开时操作实体。您可以通过调用实体上的任何方法来实现。
或者更好的替代用途findById(ID id)


为什么 API 这么不清楚?

最后,向 Spring-Data-JPA 开发人员提出两个问题:

  • 为什么没有更清晰的文档getOne()?实体延迟加载真的不是一个细节。

  • 为什么需要引入getOne()wrap EM.getReference()
    为什么不简单地坚持包装方法:getReference()getOne() 这种 EM 方法在传达如此简单的处理 时确实非常特别。

2022-08-02