当使用getOne和findOne方法时,Spring Data JPA

Man*_*dan 135 jpa spring-data spring-data-jpa

我有一个用例,它调用以下内容:

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
    return this.userControlRepository.getOne(id);
}
Run Code Online (Sandbox Code Playgroud)

观察@Transactionalhas Propagation.REQUIRES_NEW,存储库使用getOne.当我运行该应用程序时,收到以下错误消息:

Exception in thread "main" org.hibernate.LazyInitializationException: 
could not initialize proxy - no Session
...
Run Code Online (Sandbox Code Playgroud)

但是,如果我改变getOne(id)findOne(id)一切工作正常.

顺便说一下,就在用例调用getUserControlById方法之前,它已经调用了insertUserControl方法

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
    return this.userControlRepository.save(userControl);
}
Run Code Online (Sandbox Code Playgroud)

两种方法都是Propagation.REQUIRES_NEW,因为我正在进行简单的审计控制.

我使用该getOne方法,因为它是在JpaRepository接口中定义的,我的Repository接口从那里扩展,我当然正在使用JPA.

JpaRepository接口扩展自CrudRepository.该findOne(id)方法定义于CrudRepository.

我的问题是:

  1. 为什么失败的getOne(id)方法?
  2. 什么时候应该使用这种getOne(id)方法?

我正在使用其他存储库并且所有使用该getOne(id)方法并且所有工作正常,只有当我使用Propagation.REQUIRES_NEW它失败时.

根据getOne API:

返回对具有给定标识符的实体的引用.

根据findOne API:

按ID查找实体.

3)什么时候应该使用这种findOne(id)方法?

4)建议使用哪种方法?

提前致谢.

Mar*_*lmo 115

基本的区别是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!!!
}
Run Code Online (Sandbox Code Playgroud)

  • 不延迟加载意味着它只会在要使用实体时加载吗?所以我希望 getEnt 为空,如果不执行第二个中的代码,请解释一下。谢谢! (2认同)

dav*_*xxx 100

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);
Run Code Online (Sandbox Code Playgroud)

现在,findOne()您将找到的单个方法CrudRepository是在QueryByExampleExecutor接口中定义的方法:

<S extends T> Optional<S> findOne(Example<S> example);
Run Code Online (Sandbox Code Playgroud)

最后通过接口SimpleJpaRepository的默认实现来CrudRepository实现.
此方法是通过示例搜索查询,您不希望将其作为替换.

实际上,具有相同行为的方法仍然存在于新API中,但方法名称已更改.
它是从更名findOne()findById()CrudRepository接口:

Optional<T> findById(ID id); 
Run Code Online (Sandbox Code Playgroud)

现在它返回一个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));
}
Run Code Online (Sandbox Code Playgroud)

em.find()是一个EntityManager声明为的方法:

public <T> T find(Class<T> entityClass, Object primaryKey,
                  Map<String, Object> properties);
Run Code Online (Sandbox Code Playgroud)

它的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);
}
Run Code Online (Sandbox Code Playgroud)

em.getReference()是一个EntityManager声明为的方法:

public <T> T getReference(Class<T> entityClass,
                              Object primaryKey);
Run Code Online (Sandbox Code Playgroud)

幸运的是,EntityManagerjavadoc更好地定义了它的意图(重点是我的):

获取一个实例,其状态可能会被懒惰地取出.如果数据库中不存在请求的实例,则在首次访问实例状态时将引发EntityNotFoundException .(在调用getReference时,允许持久性提供程序运行时抛出EntityNotFoundException.)除非在实体管理器打开时应用程序访问实例状态,否则应用程序不应期望实例状态在分离时可用.

因此,调用getOne()可能会返回一个延迟获取的实体.
这里,延迟提取不是指实体的关系,而是实体本身.

这意味着如果我们调用getOne()然后关闭Persistence上下文,则实体可能永远不会被加载,因此结果实际上是不可预测的.
例如,如果代理对象是序列化的,则可以将null引用作为序列化结果获取,或者如果在代理对象上调用方法,LazyInitializationException则会引发异常,例如抛出.
因此,在这种情况下,抛出EntityNotFoundException这是getOne()用于处理数据库中不存在的实例的主要原因,因为在实体不存在时可能永远不会执行错误情况.

在任何情况下,为了确保其加载,您必须在会话打开时操纵实体.您可以通过调用实体上的任何方法来完成此操作.
或者更好的替代用途findById(ID id)而不是.


为什么API如此不清楚?

为了完成,Spring-Data-JPA开发人员有两个问题:

  • 为什么没有更清晰的文档getOne()?实体延迟加载实际上不是一个细节.

  • 为什么需要介绍getOne()包装EM.getReference()
    为什么不简单地坚持包裹的方法:getReference()?这种EM方法非常特别,同时getOne() 传达了如此简单的处理.

  • 我很困惑为什么 getOne() 没有抛出 EntityNotFoundException,但是您的“第一次访问实例状态时抛出 EntityNotFoundException”向我解释了这个概念。谢谢 (5认同)
  • 这个答案的摘要:“getOne()”使用延迟加载,如果找不到项目,则会抛出“EntityNotFoundException”。`findById()` 立即加载,如果未找到则返回 null。由于 getOne() 存在一些不可预测的情况,建议使用 findById() 代替。 (3认同)

Don*_*ler 71

1.为什么getOne(id)方法失败?

请参阅文档中的此部分.覆盖已就位的事务可能会导致问题.但是,如果没有更多信息,这个很难回答.

2.什么时候应该使用getOne(id)方法?

如果不深入研究Spring Data JPA的内部结构,差异似乎在于用于检索实体的机制.

如果你看的JavaDocgetOne(ID)另请参见:

See Also:
EntityManager.getReference(Class, Object)
Run Code Online (Sandbox Code Playgroud)

似乎这个方法只是委托给JPA实体管理器的实现.

但是,文档findOne(ID)没有提到这一点.

线索也在存储库的名称中. JpaRepository是特定于JPA的,因此如果需要,可以将调用委托给实体管理器. CrudRepository与所使用的持久性技术无关.看这里.它被用作多种持久性技术的标记接口,如JPA,Neo4J等.

因此,对于您的用例,这两种方法并没有真正的"差异",它只是findOne(ID)比更专业的更通用getOne(ID).您使用哪一个取决于您和您的项目,但我会亲自坚持,findOne(ID)因为它使您的代码更少的实现特定,并打开大门移动到像MongoDB等未来没有太多重构:)

  • 我认为在这里说"这两种方法中没有'真正的'差异'是非常误导的,因为实体的检索方式和应该返回的方法确实存在很大差异.@davidxxx进一步强调了这一点,我认为使用Spring Data JPA的每个人都应该意识到这一点.否则,它会引起相当多的头痛. (19认同)

小智 16

这些getOne方法仅返回DB(延迟加载)的引用.所以基本上你是在事务之外(Transactional你没有考虑在服务类中声明),并且发生错误.