当前位置: 首页 > 知识库问答 >
问题:

当使用getOne和findOne方法时,Spring Data JPA

夏侯星洲
2023-03-14

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

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

注意到@transaction具有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方法,因为它是在JPararePository接口中定义的,我的存储库接口从那里扩展过来的,当然我使用的是JPA。

JPararePository接口扩展自CrudRepository。findone(id)方法在CrudRepository中定义。

我的问题是:

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

我正在处理其他存储库,所有存储库都使用getOne(id)方法,并且所有存储库都运行良好,只是当我使用propagation.requires_new时它失败了。

根据getOne API:

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

符合findOne API:

按实体的ID检索实体。

什么时候应该使用findone(id)方法?

建议使用什么方法?

共有1个答案

单喜
2023-03-14

t findOne(ID ID)(旧API中的名称)/可选的 findById(ID ID) (新API中的名称)依赖于执行实体加载的EntityManager.find()

t getOne(ID ID)依赖于执行实体惰性加载的EntityManager.getReference()。因此,为了确保实体的有效加载,需要在实体上调用一个方法。

findone()/findById()使用起来确实比getOne()更清晰、更简单。
因此,在大多数情况下,使用findone()/findById()要优于使用getOne()

至少从2.0版本开始,spring-data-jpa修改了findone()
以前,它在CrudRepository接口中定义为:

T findOne(ID primaryKey);

现在,您将在CrudRepository中找到的单个findone()方法是在QueryByExampleExecutor接口中定义为:

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

该方法最终由SimpleJparePository实现,它是CrudRepository接口的默认实现。
此方法是按示例搜索的查询,您不希望将其作为替换。

实际上,具有相同行为的方法在新API中仍然存在,但方法名称已更改。
CrudRepository接口中将其从FindOne()重命名为FindById():

Optional<T> findById(ID id); 

现在它返回一个optional。这对于防止NullPointerException并不是很坏。

因此,现在实际的选择是在optional findById(ID ID) tgetOne(ID ID)之间进行。

1)可选 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));
}
public <T> T find(Class<T> entityClass, Object primaryKey,
                  Map<String, Object> properties);

其javadoc状态为:

使用指定属性按主键查找

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

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

实际上,参考术语实际上是board,而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。)应用程序不应期望实例状态在分离时可用,除非它在实体管理器打开时被应用程序访问。

因此,调用getOne()可能返回一个懒散提取的实体。
这里,懒散提取不是指实体的关系,而是指实体本身。

这意味着如果我们调用getOne()并关闭持久性上下文,该实体可能从未加载,因此结果确实不可预测。
例如,如果序列化代理对象,您可能将null引用作为序列化结果,或者如果在代理对象上调用方法,则引发LazyInitializationException之类的异常
因此,在这种情况下,当该实体不存在时,可能从未执行EntityNotFoundException的抛出,该抛出是使用GetOne()处理数据库中不存在的实例的主要原因。

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

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

>

  • 为什么不为getOne()提供更清晰的文档?实体懒加载真的不是一个细节。

    为什么需要引入getOne()来包装EM.getReference()
    为什么不简单地坚持包装的方法:getReference()?这个EM方法非常特别,而getOne()传达了一个如此简单的处理。

  •  类似资料:
    • 问题内容: 我读到它是延迟加载的,并立即获取整个实体。我检查调试日志,我甚至能监视我的SQL服务器上看到的语句被执行的东西,我发现,无论和生成和执行相同的查询。但是,当我使用时,这些值最初为null(当然,除了id)。 因此,谁能告诉我,如果两种方法都在数据库上执行相同的查询,那为什么我要在另一种方法上使用呢?我基本上是在寻找一种获取实体而不获取其所有子代/属性的方法。 编辑1: 实体代码 道代码

    • 问题内容: 我在项目中使用Spring 5。直到今天,仍然有可用的方法。 但是下载最新的快照后,它突然消失了!有没有参考说明该方法现在不可用? 我的依赖项列表: 更新: 似乎此方法已被替换为 问题答案: 请参阅与该提交关联的DATACMNS-944,它具有以下重命名

    • 我在我的项目中使用Spring 5。直到今天,还有可用的方法。 但下载最新快照后,它突然消失了!有没有参考现在没有的方法? 我的依赖项列表: 更新: 似乎此方法已被

    • 我不知道PATCH和PUT方法之间的确切区别。有人能告诉我什么时候我们应该用适当的例子来使用这个方法吗?

    • 我正在努力做到没有方法,以便更好地把握概念。 我真的很接近。我的数学课好像没课了。有什么我不明白的? 输出:

    • 问题内容: 我在一个教程中找到了这个示例。 当我运行此程序时,我的hs.size()值为2 ..,并且equals方法仅被调用一次。有人解释了我何时在HashSet中调用equal()方法 当上述程序中的equal方法调用时 问题答案: 您的代码将调用的唯一一次。另一次调用方法将是对的类。 此答案说明何时由和何时调用方法。摘录: HashSet利用哈希码来加快处理速度。假定彼此相等的两个对象将具有