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

时间:2014-06-30 02:09:30

标签: jpa spring-data spring-data-jpa

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

@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),则一切正常。

BTW,就在用例调用 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)方法?
  3. 我正在使用其他存储库并且都使用getOne(id)方法并且一切正常,只有当我使用 Propagation.REQUIRES_NEW 时它才会失败。

    根据 getOne API:

      

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

    根据 findOne API:

      

    按其ID检索实体。

    3)何时应该使用findOne(id)方法?

    4)建议使用哪种方法?

    提前致谢。

7 个答案:

答案 0 :(得分:105)

基本区别在于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!!!
}

答案 1 :(得分:84)

TL; DR

T findOne(ID id)(旧API中的名称)/ Optional<T> findById(ID id)(新API中的名称)依赖于执行实体预先加载EntityManager.find()。< / p>

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中,但方法名称已更改 它已在CrudRepository界面中从findOne()重命名为findById()

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   在首次访问实例状态时抛出 。 (持久性   允许提供者运行时抛出EntityNotFoundException   当调用getReference时。)应用程序不应该期望   除非是,否则实例状态将在分离时可用   在实体经理开放时由应用程序访问。

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

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

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

为什么API如此不清楚?

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

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

  • 为什么需要引入getOne()来包裹EM.getReference()? 为什么不简单地坚持包裹的方法:getReference()? 这种EM方法非常特别,而getOne()传达了如此简单的处理。

答案 2 :(得分:68)

<强> 1。为什么getOne(id)方法失败?

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

<强> 2。什么时候应该使用getOne(id)方法?

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

如果您查看下的getOne(ID)的{​​{3}},请参阅

See Also:
EntityManager.getReference(Class, Object)

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

但是findOne(ID)的{​​{3}}没有提到这一点。

线索也在存储库的名称中。 JpaRepository是特定于JPA的,因此可以在需要时委托对实体管理器的调用。 CrudRepository与所使用的持久性技术无关。 JavaDoc。它被用作JPA,docs等多种持久性技术的标记接口。

因此,并没有真正的差异&#39;在针对您的用例的两种方法中,findOne(ID)比更专业的getOne(ID)更通用。您使用哪一个取决于您和您的项目,但我个人会坚持使用findOne(ID),因为它会使您的代码更少针对特定实现,并且打开大门,以便将来转移到MongoDB等等,而无需进行太多重构:)

答案 3 :(得分:15)

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

答案 4 :(得分:1)

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

我已经测试了spring + hibernate + dozer + Mysql项目。要清楚。

我有User实体,Book Entity。你进行映射计算。

将多本书与一位用户联系起来。但在UserServiceImpl中,我试图通过getOne(userId);

找到它
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;
}

其余结果是

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

}

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

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

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

}

答案 5 :(得分:-1)

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

答案 6 :(得分:-2)

我有一个类似的问题,理解为什么JpaRespository.getOne(id)无法正常工作并引发错误。

我去了,转到JpaRespository.findById(id),它要求您返回一个Optional。

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