Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hibernate in Quarkus does not lazy load field when it is not based on the Primary Key from the parent table #35310

Closed
murilo-develop opened this issue Aug 10, 2023 · 6 comments · Fixed by #36978
Labels
area/hibernate-orm Hibernate ORM area/persistence kind/bug Something isn't working triage/upstream Used for issues which are caused by issues in upstream projects/dependency
Milestone

Comments

@murilo-develop
Copy link

murilo-develop commented Aug 10, 2023

Describe the bug

In the previous version of Quarkus (2.16.9.Final) using the Hibernate (5.6.15.Final) the lazy load process of a field which is not based on the Primary Key from the parent table worked well.

However, after upgrade the Quarkus version to 3.2.3.Final and Hibernate to 6.2.7.Final, this behavior stopped. Now, I'm not able to lazy load a field which is not based on the Primary Key from the parent table.

Expected behavior

The test should pass and in the logs we should see two SQL Select Statement to get data from the database.

Hibernate: select myentityb0_.id as id1_1_0_, myentityb0_.field as field2_1_0_ from MyEntityB myentityb0_ where myentityb0_.id=? Hibernate: select myentityb_.field as field2_1_ from MyEntityB myentityb_ where myentityb_.id=? Hibernate: select myentity0_.id as id1_0_0_, myentity0_.field as field2_0_0_ from MyEntity myentity0_ where myentity0_.field=? [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 18.074 s - in com.mda.MyEntityTest 2023-08-10 13:57:50,571 INFO [io.quarkus] (main) quarkus-hibernate stopped in 0.014s [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ quarkus-hibernate --- [INFO] Building jar: {HOME}/my-dev-work/quarkus-hibernate/target/quarkus-hibernate-1.0.0-SNAPSHOT.jar [INFO] [INFO] --- quarkus-maven-plugin:2.16.9.Final:build (default) @ quarkus-hibernate --- [INFO] [org.hibernate.Version] HHH000412: Hibernate ORM core version 5.6.15.Final [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 1524ms [INFO] [INFO] --- maven-failsafe-plugin:3.0.0-M7:integration-test (default) @ quarkus-hibernate --- [INFO] Tests are skipped. [INFO] [INFO] --- maven-failsafe-plugin:3.0.0-M7:verify (default) @ quarkus-hibernate --- [INFO] Tests are skipped. [INFO] [INFO] --- maven-install-plugin:2.4:install (default-install) @ quarkus-hibernate --- [INFO] Installing {HOME}/my-dev-work/quarkus-hibernate/target/quarkus-hibernate-1.0.0-SNAPSHOT.jar to {HOME}/.m2/repository/com/mda/quarkus-hibernate/1.0.0-SNAPSHOT/quarkus-hibernate-1.0.0-SNAPSHOT.jar [INFO] Installing {HOME}/my-dev-work/quarkus-hibernate/pom.xml to {HOME}/.m2/repository/com/mda/quarkus-hibernate/1.0.0-SNAPSHOT/quarkus-hibernate-1.0.0-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 24.559 s [INFO] Finished at: 2023-08-10T13:57:52+01:00 [INFO] ------------------------------------------------------------------------

Actual behavior

In Quarkus version 3.2.3.Final and Hibernate version 6.2.7.Final the field is not lazy loaded.

`Hibernate:
select
m1_0.id,
m1_0.field
from
MyEntityB m1_0
where
m1_0.id=?
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 19.194 s <<< FAILURE! - in com.mda.MyEntityTest
[ERROR] com.mda.MyEntityTest.shouldDo Time elapsed: 0.384 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: not
at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
at org.junit.jupiter.api.AssertNotNull.failNull(AssertNotNull.java:49)
at org.junit.jupiter.api.AssertNotNull.assertNotNull(AssertNotNull.java:35)
at org.junit.jupiter.api.AssertNotNull.assertNotNull(AssertNotNull.java:30)
at org.junit.jupiter.api.Assertions.assertNotNull(Assertions.java:301)
at com.mda.MyEntityTest.shouldDo(MyEntityTest.java:40)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at io.quarkus.test.junit.QuarkusTestExtension.runExtensionMethod(QuarkusTestExtension.java:1015)
at io.quarkus.test.junit.QuarkusTestExtension.interceptTestMethod(QuarkusTestExtension.java:829)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.apache.maven.surefire.junitplatform.LazyLauncher.execute(LazyLauncher.java:50)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:184)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:148)
at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:122)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)

2023-08-10 14:02:32,690 INFO [io.quarkus] (main) quarkus-hibernate-test stopped in 0.016s
[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR] MyEntityTest.shouldDo:40 expected: not
[INFO]
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 24.758 s
[INFO] Finished at: 2023-08-10T14:02:33+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.0.0:test (default-test) on project quarkus-hibernate-test: There are test failures.
[ERROR]
[ERROR] Please refer to {HOME}/my-dev-work/quarkus-hibernate-test/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException`

How to Reproduce?

Create a Quarkus application in https://code.quarkus.io/ based on version 3.2.3.Final
Add to the project the dependencies:
Hibernate Validator
Hibernate ORM
JDBC Driver - PostgreSQL

Add these classes below in the most convenient package for you.

`package {package-name};

import java.io.Serializable;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@entity
public class MyEntity implements Serializable {
@id
@GeneratedValue
public Long id;

@Column(nullable = false, unique = true)
public String field;

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getField() {
    return field;
}

public void setField(String field) {
    this.field = field;
}

}
`

`package {package-name};

import java.io.Serializable;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;

@entity
public class MyEntityB implements Serializable {
@id
@GeneratedValue
public Long id;

@Column
private String field;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "field", referencedColumnName = "field", insertable = false, updatable = false)
private MyEntity myEntity;

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getField() {
    return field;
}

public void setField(String field) {
    this.field = field;
}

public MyEntity getMyEntity() {
    return myEntity;
}

public void setMyEntity(MyEntity myEntity) {
    this.myEntity = myEntity;
}

}
`

Following the same package strategy choose in the previous step, add the test class below.

`package {package-name};

import static org.junit.jupiter.api.Assertions.assertNotNull;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@QuarkusTest
public class MyEntityTest {
Logger logger = LoggerFactory.getLogger(MyEntityTest.class);

@Inject
EntityManager entityManager;

private Long myEntityBId = null;

@BeforeEach
@Transactional
void setup() {
    MyEntity myEntity = new MyEntity();
    myEntity.setField("Any");
    entityManager.persist(myEntity);

    MyEntityB myEntityB = new MyEntityB();
    myEntityB.setField("Any");
    entityManager.persist(myEntityB);
    myEntityBId = myEntityB.getId();
}

@Test
void shouldDo() {
    MyEntityB myEntityB = entityManager.find(MyEntityB.class, myEntityBId);
    assertNotNull(myEntityB.getMyEntity());
}

}`

Now perform the test and check the result.

Output of uname -a or ver

Linux L-PW03AH72 5.15.90.1-microsoft-standard-WSL2 #1 SMP Fri Jan 27 02:56:13 UTC 2023 x86_64 GNU/Linux

Output of java -version

Picked up JAVA_TOOL_OPTIONS: -Dos.version=arch openjdk version "17.0.5" 2022-10-18 LTS OpenJDK Runtime Environment Zulu17.38+21-CA (build 17.0.5+8-LTS) OpenJDK 64-Bit Server VM Zulu17.38+21-CA (build 17.0.5+8-LTS, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

3.2.3.Final

Build tool (ie. output of mvnw --version or gradlew --version)

Picked up JAVA_TOOL_OPTIONS: -Dos.version=arch Apache Maven 3.8.8 (4c87b05d9aedce574290d1acc98575ed5eb6cd39) Maven home: {HOME}/.m2/wrapper/dists/apache-maven-3.8.8-bin/67c30f74/apache-maven-3.8.8 Java version: 17.0.5, vendor: Azul Systems, Inc., runtime: {HOME}/.asdf/installs/java/zulu-17.38.21 Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "arch", arch: "amd64", family: "unix"

Additional information

I have tested in a raw Java project only with Hibernate 6.2.7.Final and it worked as supposed to.

@murilo-develop murilo-develop added the kind/bug Something isn't working label Aug 10, 2023
@quarkus-bot
Copy link

quarkus-bot bot commented Aug 10, 2023

/cc @Sanne (hibernate-orm), @gsmet (hibernate-orm), @yrodiere (hibernate-orm)

@yrodiere
Copy link
Member

Your test doesn't set the association between from MyEntity and MyEntityB. So there's nothing to load lazily.

@BeforeEach
@Transactional
void setup() {
    MyEntity myEntity = new MyEntity();
    myEntity.setField("Any");
    entityManager.persist(myEntity);

    MyEntityB myEntityB = new MyEntityB();
    myEntityB.setField("Any");
    entityManager.persist(myEntityB); // Never called myEntityB.setMyEntity()!
    myEntityBId = myEntityB.getId();
}

I don't see why myEntityB.getMyEntity() would be non-null in these circumstances.

Please provide a test that doesn't pass on Quarkus 2 but passes on Quarkus 3, and I will have another look.

Also, please format your code properly so we can read it without squinting :) https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks

@yrodiere yrodiere added the triage/needs-reproducer We are waiting for a reproducer. label Aug 10, 2023
@yrodiere
Copy link
Member

Oh my, I just got what you did. Basically field is mapped twice. Yeah, code formatting would definitely have helped here...

I'll try to see what is going on.

@yrodiere yrodiere removed the triage/needs-reproducer We are waiting for a reproducer. label Aug 10, 2023
@murilo-develop
Copy link
Author

Sorry, I think I should add more detail then.

The logic behind the test is, in a new transaction I inserted some data in the database, to be quicker I did that in the method called "setup", which I annotated with @beforeeach and @transactional. So far, so good.

Now in the test method:

@test
void shouldDo() {
MyEntityB myEntityB = entityManager.find(MyEntityB.class, myEntityBId);
assertNotNull(myEntityB.getMyEntity());
}

I'm testing in there if the field myEntity in the MyEntityB would be lazy loaded. And it is not and the test will fail.

Please, you can try to reproduce that in the project attached to the comment.

quarkus-hibernate-test.zip

@yrodiere
Copy link
Member

Thanks for the reproducer, this appears to be a bug in Hibernate ORM: https://hibernate.atlassian.net/browse/HHH-17075

It'll get fixed in Quarkus when we upgrade to a version of Hibernate ORM that fixes the bug (there isn't any at the moment).

@yrodiere yrodiere added the triage/upstream Used for issues which are caused by issues in upstream projects/dependency label Aug 14, 2023
@murilo-develop
Copy link
Author

Hi @yrodiere !

Thanks for the reply! let's wait then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/hibernate-orm Hibernate ORM area/persistence kind/bug Something isn't working triage/upstream Used for issues which are caused by issues in upstream projects/dependency
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants