Summary:
After upgrading to Spring Boot 3.4.0, there is a Hibernate related error when a transactional method, after saving a row, tries to retrieve some data from a repository method using EntityGraph. This worked in Spring Boot 3.3.5
Details:
TestController calls addPayment from TestService1. This method is transactional.
This method calls the save() method of a repository
Then it calls another service TestService2 method2
This method performs a query using a repository and then another query from another repository which uses EntityGraph.
The exception is
2024-12-09T19:04:46.304+02:00 ERROR 24764 --- [errorCheck] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.EntityEntry.getMaybeLazySet()" because "entityEntry" is null] with root cause
java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.EntityEntry.getMaybeLazySet()" because "entityEntry" is null
at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstanceSubInitializers(EntityInitializerImpl.java:625) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:988) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:97) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
While trying to reproduce the issue i got another exception, but not constantly
an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: possible non-threadsafe access to the session
If we change the version of Spring Boot to 3.3.5, this works
Steps to reproduce: 1. Run the attached project using postgres as database. errorCheck.zip
- Access "http://localhost:8080/test" and an exception will be produced
2024-12-09T19:04:46.304+02:00 ERROR 24764 --- [errorCheck] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.EntityEntry.getMaybeLazySet()" because "entityEntry" is null] with root cause
If we change the Spring Boot version to 3.3.5 the endpoint will return the response "1"
Environment: Spring Boot version: 3.4.0 Java version: 21 Operating System: Windows 11
Attachments Sample project to reproduce the error
Comment From: philwebb
Here's a delomboked version of the sample with a readme for running postgres https://github.com/philwebb/gh-43447
Comment From: philwebb
This looks like a Hibernate issue to me as adding the following to the pom.xml also resolves the issue
<properties>
<java.version>21</java.version>
<hibernate.version>6.5.3.Final</hibernate.version>
</properties>
Comment From: dimcookies
Thank you @philwebb for your prompt reply. If this is the case, is this going to be included in one of the upcoming 3.4.X releases?
Comment From: philwebb
Let me investigate a bit more to see if we can identify the cause. I'll update this issue when I have more info.
Comment From: philwebb
I think this is an error in your addPayment method. The original looked like this:
@Transactional
public int addPayment(Long userId) {
Payment payment = new Payment();
var invoice = new Invoice();
invoice.setId(1L);
payment.setInvoice(invoice);
AppUser appUser = new AppUser();
appUser.setId(userId);
payment.setCreatedFrom(appUser);
this.paymentRepository.save(payment);
var test = this.testService2.method2(invoice.getId());
return test.size();
}
This is manually creating Invoice and AppUser instances outside of Hibernates control.
If you instead fetch the entities as follows, things appear to work:
@Transactional
public int addPayment(Long userId) {
Payment payment = new Payment();
var invoice = invoiceRepository.findById(1L).get();
payment.setInvoice(invoice);
AppUser appUser = userRepository.findById(userId).get();
payment.setCreatedFrom(appUser);
this.paymentRepository.save(payment);
var test = this.testService2.method2(invoice.getId());
return test.size();
}
Comment From: dimcookies
@philwebb first of all thank you for your time and effort it took to look into my issue. I understand what you are saying, but this is just a simplified and a single case of a project that it might not be even possible to identify all these cases that will fail if perform the upgrade. I would like to understand why this used to work in the previous version of hibernate and if there is any way to preserve this functionality (other than downgrading hibernate). If you have any suggestions, for example if i should open this issue to hibernate directly, it would be really helpful
Thank you again
Comment From: bclozel
@dimcookies unfortunately, the code that supports this does not live in Spring Boot but in Hibernate. If you would like to provide feedback about your upgrade experience, please do so on the Hibernate issue tracker as there is nothing we can do here. Thanks!