I have a parent entity that needs to have a reference to the latest version of a child record.
To achieve this I added a lazy @ManyToOne
property with an @JoinFormula
that returns the latest associated child record.
When I attempt a native query for the entity I receive the exception shown below.
Hibernate Version: 5.6.5
Java 15
Spring Data 2.6.1
Why is it trying to populate a lazy loaded property? Is it a bug or am I doing something wrong?
java.lang.NullPointerException: Cannot invoke "String.toLowerCase(java.util.Locale)" because "columnName" is null
at org.postgresql.jdbc.PgResultSet.findColumnIndex(PgResultSet.java:2939)
at org.postgresql.jdbc.PgResultSet.findColumn(PgResultSet.java:2898)
at org.postgresql.jdbc.PgResultSet.getObject(PgResultSet.java:2892)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getObject(HikariProxyResultSet.java)
at org.hibernate.type.PostgresUUIDType$PostgresUUIDSqlTypeDescriptor$2.doExtract(PostgresUUIDType.java:89)
at org.hibernate.type.descriptor.sql.BasicExtractor.extract(BasicExtractor.java:47)
at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:257)
at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:253)
at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:243)
at org.hibernate.type.AbstractStandardBasicType.hydrate(AbstractStandardBasicType.java:329)
at org.hibernate.type.ManyToOneType.hydrate(ManyToOneType.java:184)
at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:3214)
at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1887)
at org.hibernate.loader.Loader.hydrateEntityState(Loader.java:1811)
at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1784)
at org.hibernate.loader.Loader.getRow(Loader.java:1624)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:748)
at org.hibernate.loader.Loader.getRowsFromResultSet(Loader.java:1047)
at org.hibernate.loader.Loader.processResultSet(Loader.java:998)
at org.hibernate.loader.Loader.doQuery(Loader.java:967)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:357)
at org.hibernate.loader.Loader.doList(Loader.java:2868)
at org.hibernate.loader.Loader.doList(Loader.java:2850)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2682)
at org.hibernate.loader.Loader.list(Loader.java:2677)
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338)
at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2181)
at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1204)
at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:177)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1617)
at org.hibernate.query.Query.getResultList(Query.java:165)
at xx.xxx.xxx.assets.ui.api.insights.data.jpa.dao.AssetInsightCustomRepositories$AssetInsightCustomRepositoryImpl.findByTenantId(AssetInsightCustomRepositories.java:262)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:639)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy184.findByTenantId(Unknown Source)
at xxx.xxx.xxx.assets.ui.api.insights.service.InsightServices$AssetInsightActionServiceImpl.findByTenantId(InsightServices.java:418)
at xxx.xxx.xxx.assets.ui.api.insights.service.InsightServices$AssetInsightActionServiceImpl$$FastClassBySpringCGLIB$$a8c13b21.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at xxx.xxx.xxx.assets.ui.api.insights.service.InsightServices$AssetInsightActionServiceImpl$$EnhancerBySpringCGLIB$$51310287.findByTenantId(<generated>)
at xxx.xxx.xxx.assets.ui.api.insights.service.InsightServicesIT.testFindAssetInsightsByTenantList(InsightServicesIT.java:656)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
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 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:95)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:91)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
The query:
public List<AssetInsightEntity> findByTenantId(final String tenantId) {
var findByTenantId = """
SELECT
asset_insight.*
FROM
asset_insight
JOIN asset USING (asset_id)
WHERE
asset.tenant_id = :tenantId
""";
return entityManager().createNativeQuery(findByTenantId, AssetInsightEntity.class)
.setParameter(TENANT_ID, tenantId)
.getResultList();
}
In the org.hibernate.loader.custom.CustomLoader.loadFromResultSet
method
cols = [[], [asset_id], [insight_id], [null]]
//This is not very nice (and quite slow):
final String[][] cols = persister == rootPersister ?
getEntityAliases()[i].getSuffixedPropertyAliases() :
getEntityAliases()[i].getSuffixedPropertyAliases( persister );
final Object[] values = persister.hydrate(
rs,
id,
object,
rootPersister,
cols,
fetchAllPropertiesRequested,
getPerPropertyEagerFetchEnabled( i ),
session
Parent Entity
package xxx.xxx.xxx.assets.ui.api.insights.data.jpa.entity;
import static lombok.AccessLevel.*;
import java.util.List;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import xxx.xxx.xxx.assets.ui.api.assets.data.jpa.entity.AssetEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.Where;
/**
* The Class AssetInsightEntity.
*/
@Entity(name = "asset_insight")
@Table(name = "asset_insight")
@Builder(toBuilder = true)
@AllArgsConstructor(access = PUBLIC)
@NoArgsConstructor(access = PROTECTED)
@Setter(value = PUBLIC)
@Getter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class AssetInsightEntity {
/** The insights id. */
@Id
@Column(nullable = false)
@EqualsAndHashCode.Include
UUID assetInsightId;
/** The asset */
@OneToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH },
optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "asset_id")
AssetEntity asset;
/** The insight id. */
@OneToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH },
optional = false, fetch = FetchType.EAGER)
@JoinColumn(name = "insight_id")
InsightEntity insight;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "asset_insight_id")
@Where(clause = "deleted_ts IS NULL")
@OrderBy(clause = "actioned_ts DESC")
@ToString.Exclude
List<AssetInsightActionEntity> actions;
@ManyToOne(fetch = FetchType.LAZY, targetEntity = AssetInsightActionEntity.class)
@JoinColumnOrFormula(formula = @JoinFormula(value = """
(SELECT
lastAction.asset_insight_action_id
FROM
asset_insight_action lastAction
WHERE
lastAction.asset_insight_id = asset_insight_id
AND lastAction.deleted_ts IS NULL
ORDER BY
lastAction.actioned_ts DESC
LIMIT 1)
"""))
@ToString.Exclude
AssetInsightActionEntity lastAction;
}
Child Entity:
package xxx.xxx.xxx.assets.ui.api.insights.data.jpa.entity;
import static java.util.Comparator.*;
import static lombok.AccessLevel.*;
import java.time.OffsetDateTime;
import java.util.Comparator;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import xxx.xxx.xxx.assets.ui.api.insights.InsightActions;
import xxx.xxx.xxx.assets.ui.api.insights.InsightResolutionReason;
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
/**
* The Class AssetInsightActionEntity.
*/
@Entity(name = "asset_insight_action")
@Table(name = "asset_insight_action")
@Builder(toBuilder = true)
@AllArgsConstructor(access = PUBLIC)
@NoArgsConstructor(access = PROTECTED)
@Setter(value = PUBLIC)
@Getter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@TypeDef(name = "pgsql_enum", typeClass = PostgreSQLEnumType.class)
public class AssetInsightActionEntity {
/** The insights id. */
@Id
@Column(nullable = false)
@EqualsAndHashCode.Include
UUID assetInsightActionId;
/** The insight id. */
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "asset_insight_id", nullable = false)
@ToString.Exclude
AssetInsightEntity assetInsight;
/** The action state */
@Column(nullable = false)
@Enumerated(EnumType.STRING)
@Type(type = "pgsql_enum")
InsightActions actionState;
/** The actioned by. */
@Column(nullable = false)
UUID actionedBy;
@Column(nullable = false, name = "actioned_ts")
OffsetDateTime actionedTs;
/** The reason. */
@Column
@Enumerated(EnumType.STRING)
@Type(type = "pgsql_enum")
InsightResolutionReason resolutionReason;
/** The notes. */
@Column
String notes;
/** The date action. */
@Column(name = "deleted_ts")
OffsetDateTime deletedTs;
}