使用 getOne 和 findOne 方法时 Spring Data JPA [英] When use getOne and findOne methods Spring Data JPA

查看:15
本文介绍了使用 getOne 和 findOne 方法时 Spring Data JPA的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

@Override@Transactional(传播=传播.REQUIRES_NEW)公共用户控制 getUserControlById(整数 id){返回 this.userControlRepository.getOne(id);}

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

线程main"中的异常org.hibernate.LazyInitializationException:无法初始化代理 - 没有会话...

但如果我将 getOne(id) 更改为 findOne(id) 一切正常.

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

@Override@Transactional(传播=传播.REQUIRES_NEW)公共用户控件插入用户控件(用户控件用户控件){返回 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 时它才会失败.p>

根据 getOne API:

<块引用>

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

根据 findOne API:

<块引用>

通过 id 检索实体.

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

  2. 推荐使用什么方法?

解决方案

TL;DR

T findOne(ID id)(旧 API 中的名称)/可选;findById(ID id)(新 API 中的名称)依赖于执行 entity eager loadingEntityManager.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);

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

<S 扩展 T>可选的 <S>findOne(Example<S>example);

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

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

可选findById(ID id);

现在它返回一个可选.防止 NullPointerException 并不是那么糟糕.

所以,现在的实际选择是在 Optional;findById(ID id)T getOne(ID id).

<小时>

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

1) 可选<T>findById(ID id) javadoc 声明它:

<块引用>

通过 id 检索实体.

当我们查看实现时,我们可以看到它依赖于 EntityManager.find() 来进行检索:

public 可选findById(ID id) {Assert.notNull(id, ID_MUST_NOT_BE_NULL);类<T>domainType = getDomainClass();如果(元数据 == null){返回 Optional.ofNullable(em.find(domainType, id));}LockModeType 类型 = metadata.getLockModeType();映射<字符串,对象>提示 = getQueryHints().withFetchGraphs(em).asMap();return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));}

这里 em.find() 是一个 EntityManager 方法,声明为:

公开T find(ClassentityClass, Object primaryKey,映射<字符串,对象>特性);

它的 javadoc 状态:

<块引用>

使用指定属性按主键查找

因此,检索加载的实体似乎是预期的.

2) 虽然 T getOne(ID id) javadoc 状态(重点是我的):

<块引用>

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

事实上,reference 术语实际上是板子,JPA API 没有指定任何 getOne() 方法.
因此,了解 Spring 包装器所做的最好的事情是研究实现:

@Override公共T getOne(ID id){Assert.notNull(id, ID_MUST_NOT_BE_NULL);返回 em.getReference(getDomainClass(), id);}

这里 em.getReference() 是一个 EntityManager 方法,声明为:

公开T getReference(ClassentityClass,对象主键);

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

<块引用>

获取一个实例,其状态可能被延迟获取.如果要求数据库中不存在实例,EntityNotFoundException在第一次访问实例状态时抛出.(坚持允许提供程序运行时抛出 EntityNotFoundException当 getReference 被调用时.)应用程序不应该期望实例状态将在分离时可用,除非它是实体管理器打开时由应用程序访问.

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

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

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

<小时>

为什么 API 这么不清楚?

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

  • 为什么没有更清晰的 getOne() 文档?实体懒加载真的不是细节.

  • 为什么要引入getOne()来包装EM.getReference()?
    为什么不简单地坚持包装方法 :getReference() ?这个 EM 方法真的很特别,而 getOne() 传达了一个如此简单的处理.

I have an use case where it calls the following:

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

Observe the @Transactional has Propagation.REQUIRES_NEW and the repository uses getOne. When I run the app, I receive the following error message:

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

But If I change the getOne(id) by findOne(id) all works fine.

BTW, just before the use case calls the getUserControlById method, it already has called the insertUserControl method

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

Both methods are Propagation.REQUIRES_NEW because I am doing a simple audit control.

I use the getOne method because it is defined in the JpaRepository interface and my Repository interface extends from there, I am working with JPA of course.

The JpaRepository interface extends from CrudRepository. The findOne(id) method is defined in CrudRepository.

My questions are:

  1. Why fail the getOne(id) method?
  2. When I should use the getOne(id) method?

I am working with other repositories and all use the getOne(id) method and all work fine, only when I use the Propagation.REQUIRES_NEW it fails.

According with the getOne API:

Returns a reference to the entity with the given identifier.

According with the findOne API:

Retrieves an entity by its id.

  1. When I should use the findOne(id) method?

  2. What method is recommended to be used?

解决方案

TL;DR

T findOne(ID id) (name in the old API) / Optional<T> findById(ID id) (name in the new API) relies on EntityManager.find() that performs an entity eager loading.

T getOne(ID id) relies on EntityManager.getReference() that performs an entity lazy loading. So to ensure the effective loading of the entity, invoking a method on it is required.

findOne()/findById() is really more clear and simple to use than getOne().
So in the very most of cases, favor findOne()/findById() over getOne().


API Change

From at least, the 2.0 version, Spring-Data-Jpa modified findOne().
Previously, it was defined in the CrudRepository interface as :

T findOne(ID primaryKey);

Now, the single findOne() method that you will find in CrudRepository is which one defined in the QueryByExampleExecutor interface as :

<S extends T> Optional<S> findOne(Example<S> example);

That is implemented finally by SimpleJpaRepository, the default implementation of the CrudRepository interface.
This method is a query by example search and you don't want to that as replacement.

In fact, the method with the same behavior is still there in the new API but the method name has changed.
It was renamed from findOne() to findById() in the CrudRepository interface :

Optional<T> findById(ID id); 

Now it returns an Optional. Which is not so bad to prevent NullPointerException.

So, the actual choice is now between Optional<T> findById(ID id) and T getOne(ID id).


Two distinct methods that rely on two distinct JPA EntityManager retrieval methods

1) The Optional<T> findById(ID id) javadoc states that it :

Retrieves an entity by its id.

As we look into the implementation, we can see that it relies on EntityManager.find() to do the retrieval :

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));
}

And here em.find() is an EntityManager method declared as :

public <T> T find(Class<T> entityClass, Object primaryKey,
                  Map<String, Object> properties);

Its javadoc states :

Find by primary key, using the specified properties

So, retrieving a loaded entity seems expected.

2) While the T getOne(ID id) javadoc states (emphasis is mine) :

Returns a reference to the entity with the given identifier.

In fact, the reference terminology is really board and JPA API doesn't specify any getOne() method.
So the best thing to do to understand what the Spring wrapper does is looking into the implementation :

@Override
public T getOne(ID id) {
    Assert.notNull(id, ID_MUST_NOT_BE_NULL);
    return em.getReference(getDomainClass(), id);
}

Here em.getReference() is an EntityManager method declared as :

public <T> T getReference(Class<T> entityClass,
                              Object primaryKey);

And fortunately, the EntityManager javadoc defined better its intention (emphasis is mine) :

Get an instance, whose state may be lazily fetched. If the requested instance does not exist in the database, the EntityNotFoundException is thrown when the instance state is first accessed. (The persistence provider runtime is permitted to throw the EntityNotFoundException when getReference is called.) The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open.

So, invoking getOne() may return a lazily fetched entity.
Here, the lazy fetching doesn't refer to relationships of the entity but the entity itself.

It means that if we invoke getOne() and then the Persistence context is closed, the entity may be never loaded and so the result is really unpredictable.
For example if the proxy object is serialized, you could get a null reference as serialized result or if a method is invoked on the proxy object, an exception such as LazyInitializationException is thrown.
So in this kind of situation, the throw of EntityNotFoundException that is the main reason to use getOne() to handle an instance that does not exist in the database as an error situation may be never performed while the entity is not existing.

In any case, to ensure its loading you have to manipulate the entity while the session is opened. You can do it by invoking any method on the entity.
Or a better alternative use findById(ID id) instead of.


Why a so unclear API ?

To finish, two questions for Spring-Data-JPA developers:

  • why not having a clearer documentation for getOne() ? Entity lazy loading is really not a detail.

  • why do you need to introduce getOne() to wrap EM.getReference() ?
    Why not simply stick to the wrapped method :getReference() ? This EM method is really very particular while getOne() conveys a so simple processing.

这篇关于使用 getOne 和 findOne 方法时 Spring Data JPA的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆