TL; DR
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声明(强调是我的):
返回对具有给定标识符的实体的引用。
实际上,参考术语实际上是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);
幸运的是,EntityManager
javadoc更好地定义了它的意图(强调是我的):
获取一个实例,其状态可能会延迟获取。如果请求的实例在数据库中不存在,则在首次访问实例状态时抛出EntityNotFoundException 。(调用getReference时,允许持久性提供程序运行时引发EntityNotFoundException。)应用程序不应期望实例状态在分离后可用,除非在打开实体管理器时由应用程序访问了实例状态。
因此,调用getOne()
可能会返回延迟获取的实体。
在这里,延迟获取不是指实体的关系,而是指实体本身。
这意味着,如果我们调用getOne()
然后关闭Persistence上下文,则该实体可能永远不会加载,因此结果实际上是不可预测的。
例如,如果代理对象已序列化,则可以获取null
引用作为序列化结果,或者如果在代理对象上调用方法,LazyInitializationException
则会引发诸如之类的异常。
因此,在这种情况下,抛出该异常是用于处理数据库中不存在的实例的EntityNotFoundException
主要原因,getOne()
因为在实体不存在时可能永远不会执行错误情况。
无论如何,要确保其加载,您必须在打开会话时操纵实体。您可以通过在实体上调用任何方法来实现。
或者更好的替代用途findById(ID id)
代替。
为什么API如此不清楚?
最后,向Spring-Data-JPA开发人员提出两个问题: