当使用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
定义。
我的问题是:
- 为什么
getOne(id)
方法失败? - 当我应该使用
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()
传达了一个如此简单的处理。
- 如何从我的控制器中加载Hibernate / JPA中的延迟获取项目
- 如何解决Spring Data Maven构build中的“生命周期configuration未涉及的插件执行”
- Spring Boot以及如何将连接细节configuration到MongoDB?
- 如何将自定义方法添加到Spring Data JPA
- Spring Boot – 无法确定数据库types为NONE的embedded式数据库驱动程序类
- 我可以使自定义控制器镜像Spring-Data-Rest / Spring-Hateoas生成的类的格式吗?
- 如何testingSpring数据仓库?
- Spring数据jpa-没有定义名为“entityManagerFactory”的bean; 注入自动装载的依赖关系失败