ChatGPT解决这个技术问题 Extra ChatGPT

When use getOne and findOne methods Spring Data JPA

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:

Why fail the getOne(id) method? 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.

When I should use the findOne(id) method? What method is recommended to be used?

You should especially not use getOne() to test the existence of an object in the data base, because with getOne you always get an object != null, whereas findOne delivers null.

d
davidxxx

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.


I was confused why getOne() is not throwing the EntityNotFoundException, but your "the EntityNotFoundException is thrown when the instance state is first accessed" explained me the concept. Thanks
Summary of this answer: getOne() uses lazy loading, and throws an EntityNotFoundException if no item is found. findById() loads right away, and returns null if not found. Since there is some unpredictable situations with getOne(), it is recommended to use findById() instead.
As of Spring Data JPA 2.5, getOne() has been deprecated in favor of the newly named getById() (just a rename of getOne() returning a reference to the entity as described above). I'd recommend using findById() to avoid unexpected exceptions.
a
andyvn22

The basic difference is that getOne is lazy loaded and findOne is not.

Consider the following example:

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!!!
}

doesnt lazy loaded means that it will only loaded when the entity is going to be used? so I would expect getEnt to be null and the code inside the second if not to be executed Could you please explain. Thanks!
If wrapped inside a CompletableFuture<> web service I've found that you'll want to use findOne() vs. getOne() because of it's lazy implementation.
P
Peter Hall

1. Why does the getOne(id) method fail?

See this section in the docs. You overriding the already in place transaction might be causing the issue. However, without more info this one is difficult to answer.

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

Without digging into the internals of Spring Data JPA, the difference seems to be in the mechanism used to retrieve the entity.

If you look at the JavaDoc for getOne(ID) under See Also:

See Also:
EntityManager.getReference(Class, Object)

it seems that this method just delegates to the JPA entity manager's implementation.

However, the docs for findOne(ID) do not mention this.

The clue is also in the names of the repositories. JpaRepository is JPA specific and therefore can delegate calls to the entity manager if so needed. CrudRepository is agnostic of the persistence technology used. Look here. It's used as a marker interface for multiple persistence technologies like JPA, Neo4J etc.

So there's not really a 'difference' in the two methods for your use cases, it's just that findOne(ID) is more generic than the more specialised getOne(ID). Which one you use is up to you and your project but I would personally stick to the findOne(ID) as it makes your code less implementation specific and opens the doors to move to things like MongoDB etc. in the future without too much refactoring :)


I think it is very misleading to say that there's not really a 'difference' in the two methods here, because there really is a big difference in how the entity is retrieved and what you should expect the method to return. The answer further down by @davidxxx highlights this very well, and I think everyone who uses Spring Data JPA should be aware of this. Otherwise, it can cause quite a bit of headache.
n
naXa stands with Ukraine

The getOne methods returns only the reference from DB (lazy loading). So basically you are outside the transaction (the Transactional you have been declare in service class is not considered), and the error occur.


Seems EntityManager.getReference(Class, Object) returns "nothing" since we are in a new Transaction scope.
E
EngineSense

I really find very difficult from the above answers. From debugging perspective i almost spent 8 hours to know the silly mistake.

I have testing spring+hibernate+dozer+Mysql project. To be clear.

I have User entity, Book Entity. You do the calculations of mapping.

Were the Multiple books tied to One user. But in UserServiceImpl i was trying to find it by 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;
}

The Rest result is

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

}

The above code did not fetch the books which is read by the user let say.

The bookList was always null because of getOne(ID). After changing to findOne(ID). The result is

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

}


F
Farshad Falaki

while spring.jpa.open-in-view was true, I didn't have any problem with getOne but after setting it to false , i got LazyInitializationException. Then problem was solved by replacing with findById. Although there is another solution without replacing the getOne method, and that is put @Transactional at method which is calling repository.getOne(id). In this way transaction will exists and session will not be closed in your method and while using entity there would not be any LazyInitializationException.


a
akshaymittal143

I had a similar problem understanding why JpaRespository.getOne(id) does not work and throw an error.

I went and change to JpaRespository.findById(id) which requires you to return an Optional.

This is probably my first comment on StackOverflow.


Unfortunately, this does not provide and answer to the question, nor improves the existing answers.
I see, no problem.