NaturalId, Hibernate always hit the DB

Hi all,

I have a strange behavior when using relation through naturalId instead of PK.

You can find the examples and tests here : https://gitlab.com/pkernevez/hibernate-questions

I have a Position class that has 2 fields Amount.
An Amount is Embeddable and has 2 fields: a quantity and a link to a Currency.
The link with Currency is tested with Lazy and Eager configuration.

When the link is using the PK, Hibernate has a logical behavior:

  • With Eager: Hibernate loads the position & the Currencies, without additional request
  • With Lazy: Hibernate loads the position and the currencies with the minimum requests, meaning one per ID.

But when the link is using the Natural ID I have a lot of additional useless requests I can’t explain.
With the test CurrencyEntityTest wa can check that the naturalId L1 cache is well used when doing a request in my code with the NaturalId.

But when I’m loading Position, additional request on Currency are executed.

With Eager (PositionEntityTest#testPassWithAmountEagerStrategy), we can see that Hibernate is doing 2 additional requests on Currency even it already retrieve all the Currency data in the position request.
You can fin more explanation in the tests.

With Lazy (PositionEntityTest#testPassWithAmountLazyStrategy), it reloads multiple times the same currency (‘CHF’), instead of using the L1 Cache.

When Lazy or Eager and loading multiple Positions in one find request without accessing to the currency it always loads the Currency (twice for CHF and twice for EUR).

Why when using a naturalId:
1- With Eager Hibernate do additional request to retrieve data it already has and load Currency with additional requests ?
2- With Lazy, why does it load the same Currency multiple times ?
3- With Lazy or Eager why does it load Currency (multiple times) when not accessing them ?

Thanks for your help,
Philippe

Did you try the same with Hibernate 6 for example 6.1.2.Final? We implemented a lot of improvements in this area, so I guess this might be already fixed. Since Hibernate 5 is in maintenance mode, we won’t backport such improvements. Please report back if this works for you with Spring 3.0, which will use Hibernate 6

I didn’t, but now I tried.
I put a branch with Spring3 and Hibernate6.1.1, the code is here Files · hibernate6 · Philippe Kernevez / hibernate-questions · GitLab

I have an issue when trying to run the test with the NaturalId and Lazy mode (it’s ok for the PK code and ok with NaturalId+Eager).

For the Eager configuration, it solve my unexpected additional request :slight_smile:

For the Lazy configuration do you have an idea of the cause of the Exception ?

java.lang.ClassCastException: class org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchInitializer cannot be cast to class org.hibernate.sql.results.graph.entity.AbstractEntityInitializer (org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchInitializer and org.hibernate.sql.results.graph.entity.AbstractEntityInitializer are in unnamed module of loader 'app')

Full stack:

java.lang.ClassCastException: class org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchInitializer cannot be cast to class org.hibernate.sql.results.graph.entity.AbstractEntityInitializer (org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchInitializer and org.hibernate.sql.results.graph.entity.AbstractEntityInitializer are in unnamed module of loader 'app')

	at org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer.resolveInstance(EntityDelayedFetchInitializer.java:136)
	at org.hibernate.sql.results.internal.StandardRowReader.coordinateInitializers(StandardRowReader.java:148)
	at org.hibernate.sql.results.internal.StandardRowReader.readRow(StandardRowReader.java:98)
	at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:143)
	at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:32)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:437)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:166)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.list(JdbcSelectExecutorStandardImpl.java:91)
	at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:31)
	at org.hibernate.loader.ast.internal.SingleIdLoadPlan.load(SingleIdLoadPlan.java:140)
	at org.hibernate.loader.ast.internal.SingleIdLoadPlan.load(SingleIdLoadPlan.java:110)
	at org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl.load(SingleIdEntityLoaderStandardImpl.java:72)
	at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:4313)
	at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4303)
	at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:598)
	at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:571)
	at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:223)
	at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:107)
	at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:74)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:118)
	at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1237)
	at org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1041)
	at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:173)
	at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
	at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44)
	at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
	at net.kernevez.hibernate.entity.withnatural.PositionEntity$HibernateProxy$xu6tjXIg.getNetAmount(Unknown Source)
	at net.kernevez.hibernate.entity.withnatural.PositionEntityTest$WithLazy.testPassWithAmountLazyStrategy(PositionEntityTest.java:97)
	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 org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	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.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	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 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:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	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.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

That issue is caused by a bug :slight_smile:

Could you please create an issue in the issue tracker(https://hibernate.atlassian.net) with a test case(hibernate-test-case-templates/JPAUnitTestCase.java at main · hibernate/hibernate-test-case-templates · GitHub) that reproduces the issue?

I create the issue: [HHH-15481] - Hibernate JIRA

I was not able to strictly follow your test case, because I wasn’t able t retrieve the repository with your test case and I wasn’t able to reproduce the issue with a simple query.

Thanks a lot. We will look into it as soon as possible.

Hi all,

With the new version (6.1.4Final), the Exception left.

Now we can focus on the initial point.
With a relation ManyToOne using a naturalId, the lazy mechanism is ignored.

    @ManyToOne(fetch = FetchType.LAZY)
    protected CurrencyEntity currency;
  1. Right after loading the referenceEntity, it does additional requests to load refrenced Entity.
  2. The referenceEntity reference twice the same currency, and there are 2 requests to (try) to load the same entity

The code with tests is available here: https:// gitlab . com/ pkernevez/hibernate-questions/-/tree/hibernate6-lazy-issue-with-naturalid
(this is not the master branch but the hibernate6-lazy-issue-with-naturalid branch)

There are more detail in the readme and Test comments.
The CurrencyTest test is just here to check the configuration of the naturalId and the behavior when using the API.

The PositionTest is the failing test with comment about what I expect (close to the behavior when using relation with a technical ID) and what I observe.

You can’t lazy load associations mapped through non-primary keys. Hibernate proxies require the primary key. This is why you are seeing this immediate loading. Hibernate 5 behaves the same way.

If you really want lazy loading, you will have to use bytecode enhancement. An alternative is to use a DTO approach with e.g. Blaze-Persistence Entity-Views where you simply don’t have that association in your model and thus also don’t need to care about lazy loading.

Thanks for the explaination