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