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

问题描述 投票:124回答:7

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

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

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

Exception in thread "main" org.hibernate.LazyInitializationException: 
could not initialize proxy - no Session
...

但如果我用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。

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)建议使用哪种方法?

提前致谢。

jpa spring-data spring-data-jpa
7个回答
68
投票

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

请参阅本节in the docs。覆盖已就位的事务可能会导致问题。但是,如果没有更多信息,这个很难回答。

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

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

如果你看看JavaDoc for getOne(ID),请参阅:

See Also:
EntityManager.getReference(Class, Object)

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

然而,docsfindOne(ID)没有提到这一点。

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

因此,对于您的用例,这两种方法并没有真正的“差异”,只是findOne(ID)比更专业的getOne(ID)更通用。您使用哪一个取决于您和您的项目,但我个人会坚持使用findOne(ID),因为它会降低您的代码实现特定性,并打开大门,以便将来移动到像MongoDB等等,而不需要太多的重构:)


111
投票

基本的区别是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!!!
}

88
投票

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实现,CrudRepositoryfindOne()接口的默认实现。 此方法是通过示例搜索查询,您不希望将其作为替换。

实际上,具有相同行为的方法仍然存在于新API中,但方法名称已更改。 它在findById()界面中从CrudRepository重命名为Optional<T> findById(ID id);

Optional

现在它返回一个NullPointerException。这对于防止Optional<T> findById(ID id)来说并不是那么糟糕。

所以,现在实际的选择是在T getOne(ID id)Optional<T> findById(ID id) javadoc之间。


两种不同的方法依赖于两种不同的JPA EntityManager检索方法

1)EntityManager.find()声明它:

按ID查找实体。

在我们研究实现时,我们可以看到它依赖于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); 方法声明为:

T getOne(ID id) javadoc

它的javadoc说:

使用指定的属性按主键查找

因此,预计检索加载的实体。

2)虽然getOne()国家(重点是我的):

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

实际上,参考术语实际上是板,而JPA API没有指定任何@Override public T getOne(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); return em.getReference(getDomainClass(), id); } 方法。 因此,了解Spring包装器的作用最好的方法是查看实现:

em.getReference()

这里EntityManager是一个public <T> T getReference(Class<T> entityClass, Object primaryKey); 方法,声明为:

EntityManager

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

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

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

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

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


为什么API如此不清楚?

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

  • 为什么不为getOne()提供更清晰的文档?实体延迟加载实际上不是一个细节。
  • 为什么你需要引入EM.getReference()来包装getReference()? 为什么不简单地坚持包裹的方法:getOne()?这种EM方法非常特别,而getOne传达了如此简单的处理。

15
投票

Transactional方法仅返回DB(延迟加载)的引用。所以基本上你是在事务之外(不考虑你在服务类中声明的public UserDTO getById(int userId) throws Exception { final User user = userDao.getOne(userId); if (user == null) { throw new ServiceException("User not found", HttpStatus.NOT_FOUND); } userDto = mapEntityToDto.transformBO(user, UserDTO.class); return userDto; } ),并且发生错误。


1
投票

从上面的答案中我真的很难找到。从调试的角度来看,我几乎花了8个小时才知道愚蠢的错误。

我有测试spring + hibernate + dozer + Mysql项目。要清楚。

我有用户实体,书籍实体。您进行映射计算。

是多个书籍绑定到一个用户。但在UserServiceImpl中,我试图通过getOne(userId)找到它;

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 1,
        "name": "TEST_ME",
        "bookList": null
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

其余的结果是

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 0,
        "name": "Annama",
        "bookList": [
            {
                "id": 2,
                "book_no": "The karma of searching",
            }
        ]
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}

上面的代码没有提取用户读到的书籍。

由于getOne(ID),bookList始终为null。更改为findOne(ID)后。结果是

qazxswpoi

}


-1
投票

虽然spring.jpa.open-in-view是真的,但我对getOne没有任何问题,但在将其设置为false后,我得到了LazyInitializationException。然后用findById替换问题。 虽然还有另一种解决方案,但没有替换getOne方法,而是将@Transactional放在调用repository.getOne(id)的方法中。这样,事务将存在,并且会话将不会在您的方法中关闭,并且在使用实体时,不会有任何LazyInitializationException。


-2
投票

我有一个类似的问题,理解为什么JpaRespository.getOne(id)不起作用并抛出错误。

我去了JpaRespository.findById(id),要求你返回一个Optional。

这可能是我对StackOverflow的第一个评论。