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);
现在,在
CrudRepository
中可以找到的唯一一个
findOne()
方法是在
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(我强调)声明如下:
返回具有给定标识符的引用实体。
实际上,引用术语确实很广泛,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()
,然后关闭持久性上下文,那么该实体可能永远不会被加载,因此结果是不可预测的。
例如,如果代理对象被序列化,您可能会得到一个空引用作为序列化结果,或者如果在代理对象上调用方法,则会抛出诸如
LazyInitializationException
之类的异常。
因此,在这种情况下,抛出
EntityNotFoundException
的情况(这是使用
getOne()
处理数据库中不存在的实例的主要原因)可能永远不会发生。
无论如何,为了确保其加载,您必须在会话打开时操作实体。您可以通过调用实体上的任何方法来实现这一点。
或者更好的选择是使用
findById(ID id)
。
为什么API如此不清晰?
最后,对于Spring-Data-JPA开发人员的两个问题:
1.为什么没有更清晰的
getOne()
文档?实体的延迟加载确实不是一个细节。
2.为什么需要引入
getOne()
来包装
EM.getReference()
?
为什么不简单地坚持包装方法:
getReference()
?
这个EM方法真的非常特殊,而
getOne()
传达了一个如此简单的处理。