当使用 getOne 和 findOne 方法时

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

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

观察到 @TransactionalREQUIRES _ NEW,存储库使用 得一。当我运行这个应用程序时,我收到以下错误消息:

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

但是,如果我改变 getOne(id)findOne(id)所有的工作正常。

顺便说一句,就在用例调用 GetUserControlById方法之前,它已经调用了 SertUserControl 插入用户控件方法

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

这两个方法都是 REQUIRES _ NEW,因为我正在做一个简单的 审计署控制。

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

JpaRepository接口从 原油储藏库扩展而来。 findOne(id)方法在 CrudRepository中定义。

我的问题是:

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

我与其他存储库和所有使用的 getOne(id)方法和所有工作良好,只有当我使用的 REQUIRES _ NEW它失败。

根据 得一电视宣传短片:

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

根据 找到一个电视宣传短片:

根据实体的 id 检索实体。

  1. 什么时候应该使用 findOne(id)方法?

  2. 建议使用什么方法?

244476 次浏览

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

看看这个部分 在文件里。重写已存在的事务可能会导致问题。然而,如果没有更多的信息,这个问题很难回答。

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

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

如果你在 参见下查看 JavaDocgetOne(ID):

See Also:
EntityManager.getReference(Class, Object)

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

然而,findOne(ID)医生没有提到这一点。

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

所以对于你的用例来说,这两种方法并没有什么不同,只是 findOne(ID)比专门的 getOne(ID)更通用。你使用哪一个取决于你和你的项目,但是我个人会坚持使用 findOne(ID),因为它使你的代码没有特定的实现,并且打开了在未来转移到诸如 MongoDB 之类的东西的大门,而不需要太多的重构:)

getOne方法只返回 DB (延迟加载)的引用。 因此,基本上您处于事务之外(不考虑在服务类中声明的 Transactional) ,并且发生了错误。

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

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()


空气污染指数变化

至少从 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 中,但是方法名已经更改。
它在 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指出(重点是我的) :

向具有给定标识符的实体返回 参考文献

事实上,参考文献的术语实际上是板和 JPAAPI 没有指定任何 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 时。) < strong > 应用程序不应该期望 实例状态将在分离 时可用,除非 在实体管理器打开时由应用程序访问。

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

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

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


为什么 API 这么模糊?

最后,对 Spring-Data-JPA 开发人员有两个问题:

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

  • 为什么需要引入 getOne()来包装 EM.getReference()
    为什么不简单地坚持使用包装的方法: getReference()? 这种 EM 方法确实非常特殊,而 getOne()传达了一个非常简单的处理过程

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

我有一个测试 Spring + hibernate + dozer + Mysql 的项目。

我有用户实体,书实体。你做的映射计算。

多本书是否绑定在一个用户上,但是在 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

}

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

BookList 总是空的,因为 getOne (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

}

我在理解为什么 JpaRespository.getOne (id)不工作并抛出错误时遇到了类似的问题。

我转到 JpaRespository.findById (id) ,它要求您返回一个可选项。

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

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