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

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

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

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

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

但是,如果我通过findOne(id)更改getOne(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检索一个实体。

3)当我应该使用findOne(id)方法?

4)build议使用什么方法?

提前致谢。

1.为什么getOne(id)方法失败?

请参阅文档中的这一部分。 重写已经存在的事务可能会导致问题。 但是,没有更多的信息,这个难以回答。

2.我应该使用getOne(id)方法吗?

没有深入到Spring Data JPA的内部,差别似乎是用于检索实体的机制。

如果您查看下面的getOne(ID)的JavaDoc 另请参阅

 See Also: EntityManager.getReference(Class, Object) 

看来这个方法只是委托给JPA实体经理的实现。

但是, findOne(ID)的文档没有提到这一点。

线索也在储存库的名称中。 JpaRepository是特定JpaRepository JPA的,因此可以在需要时将调用委托给实体pipe理器。 CrudRepository使用的持久性技术。 看这里 它被用作JPA, Neo4J等多种持久性技术的标记接口。

所以对于你的用例来说,这两种方法并没有什么不同,只是findOne(ID)比更专业化的getOne(ID)更通用。 你使用哪一个取决于你和你的项目,但我个人坚持findOne(ID)因为它使你的代码更less的实现特定的,并打开大门,移动到MongoDB等在未来的东西没有太多的重构: )

基本的区别是, getOne是懒加载和findOne不是。

考虑下面的例子:

 public static String NON_EXISTING_ID = -1; ... MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID); MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID); if(findEnt != null) { findEnt.getText(); // findEnt is null - this code is not executed } if(getEnt != null) { getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!! } 

getOne方法只返回来自DB的引用(延迟加载)。 所以基本上你在事务之外(你已经在服务类中声明的Transactional不被考虑),并且发生错误。

为了得到关于getOne()findOne()之间区别的其他有趣的信息,你也可以参考这篇文章和接受的答案 。

为了用Spring-Data-JPA的新API来完成这个优秀的答案和更新,我会给出一些其他的细节。


TL; DR

Optional<T> findById(ID id)包装了EntityManager.find() API,它依赖于实体急切的加载。

T getOne(ID id)包装依赖于实体延迟加载的EntityManager.getReference() API(以确保实际加载,在实体上调用方法是必需的)。


API更改

至less从2.0版本开始, Spring-Data-Jpa修改了findOne()
现在, findOne()不具有相同的签名和相同的行为。
以前,它在CrudRepository接口中定义为:

 T findOne(ID primaryKey); 

现在,它在QueryByExampleExecutor接口中被定义为

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

现在是一个查询的例子。

实际上,具有相同行为的方法在新API中仍然存在,但方法名称已更改。
它在CrudRepository接口中被定义为:

 Optional<T> findById(ID id); 

现在它返回一个Optional
防止NPE并不是那么糟糕。


所以,实际的select是在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)); } 

 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); 

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

获取一个实例,其状态可能会被延迟取出 。 如果请求的实例在数据库中不存在, 则首次访问实例状态时将引发EntityNotFoundException。 (持久化提供程序运行时允许在调用getReference时抛出EntityNotFoundException。) 应用程序不应该期望实例状态在分离时可用 ,除非应用程序在实体pipe理器打开时访问实例状态

所以,调用getOne()可能会返回一个懒惰的获取实体。
在这里,延迟提取并不是指实体的关系,而是实体本身的关系。
这意味着如果我们调用getOne()然后closuresHibernate会话,那么实体可能永远不会被加载,所以保持null

为了确保它的加载,你必须在会话打开时操作实体。 你可以通过调用实体上的任何方法来完成。
或者更好的select使用findById(ID id)而不是。


为了完成,Spring-Data-JPA开发人员提出了两个问题:

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

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