我有一个用例,它调用以下内容:
@Override @Transactional(propagation=Propagation.REQUIRES_NEW) public UserControl getUserControlById(Integer id){ return this.userControlRepository.getOne(id); }
观察@Transactionalhas Propagation.REQUIRES_NEW 并且存储库使用 getOne 。当我运行该应用程序时,我收到以下错误消息:
@Transactional
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session ...
但如果我改变所有的getOne(id)工作findOne(id)正常。
getOne(id)
findOne(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。
getOne
JpaRepository接口从 CrudRepository扩展 而来。该findOne(id)方法在 中定义CrudRepository。
CrudRepository
我的问题是:
我正在使用其他存储库,并且都使用该getOne(id)方法并且一切正常,只有当我使用 Propagation.REQUIRES_NEW 时它才会失败。
根据 getOne API:
返回对具有给定标识符的实体的引用。
根据 findOne API:
通过 id 检索实体。
我什么时候应该使用该findOne(id)方法?
推荐使用什么方法?
T findOne(ID id)(旧 API 中的Optional<T> findById(ID id)名称)/(新 API 中的名称)依赖于EntityManager.find()执行 实体预加载 。
T findOne(ID id)
Optional<T> findById(ID id)
EntityManager.find()
T getOne(ID id)依赖于EntityManager.getReference()执行 实体延迟加载 。所以为了保证实体的有效加载,需要对其调用方法。
T getOne(ID id)
EntityManager.getReference()
findOne()/findById()确实比getOne(). 所以在大多数情况下,findOne()/findById()赞成getOne().
findOne()/findById()
getOne()
从至少,2.0版本,Spring-Data-Jpa修改findOne()。 以前,它在CrudRepository接口中定义为:
2.0
Spring-Data-Jpa
findOne()
T findOne(ID primaryKey);
现在,findOne()您将找到的唯一方法CrudRepository是在接口中定义QueryByExampleExecutor为:
QueryByExampleExecutor
<S extends T> Optional<S> findOne(Example<S> example);
最终由接口SimpleJpaRepository的默认实现来实现CrudRepository。 此方法是通过示例搜索进行的查询,您不希望将其作为替换。
SimpleJpaRepository
实际上,具有相同行为的方法仍然存在于新 API 中,但方法名称已更改。 它在界面中被重命名findOne()为:findById()CrudRepository
findById()
Optional<T> findById(ID id);
现在它返回一个Optional. 这不是那么糟糕,以防止NullPointerException。
Optional
NullPointerException
所以,现在的实际选择是在Optional<T> findById(ID id)和之间T getOne(ID id)。
1)Optional<T> findById(ID id)javadoc声明它:
当我们查看实现时,我们可以看到它依赖于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声明为的方法:
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声明为的方法:
em.getReference()
public <T> T getReference(Class<T> entityClass, Object primaryKey);
幸运的是,EntityManagerjavadoc 更好地定义了它的意图(重点是我的):
获取一个实例,其状态可能会被延迟获取 。如果请求的实例在数据库中不存在,则在 第一次访问实例状态时 抛出 EntityNotFoundException 。(当调用 getReference 时,允许持久性提供程序运行时抛出 EntityNotFoundException。) 应用程序不应期望实例状态在 detachment 时可用 ,除非它在实体管理器打开时由应用程序访问。
因此,调用getOne()可能会返回一个延迟获取的实体。 这里,惰性获取不是指实体的关系,而是指实体本身。
这意味着如果我们调用getOne()然后 Persistence 上下文关闭,实体可能永远不会加载,因此结果确实是不可预测的。 例如,如果代理对象被序列化,您可以获得null作为序列化结果的引用,或者如果在代理对象上调用方法,LazyInitializationException则会引发异常。 所以在这种情况下,throw是用来处理数据库中不存在的实例EntityNotFoundException的主要原因,因为在实体不存在时可能永远不会执行错误情况。getOne()
null
LazyInitializationException
EntityNotFoundException
在任何情况下,为了确保它的加载,您必须在会话打开时操作实体。您可以通过调用实体上的任何方法来实现。 或者更好的替代用途findById(ID id)。
findById(ID id)
最后,向 Spring-Data-JPA 开发人员提出两个问题:
为什么没有更清晰的文档getOne()?实体延迟加载真的不是一个细节。
为什么需要引入getOne()wrap EM.getReference()? 为什么不简单地坚持包装方法:getReference()?getOne() 这种 EM 方法在传达如此简单的处理 时确实非常特别。
EM.getReference()
getReference()