HHH-17102 Fix @SqlResultSetMapping issue for joined inheritance entity result

This commit is contained in:
Christian Beikov 2023-11-10 15:28:00 +01:00
parent 47e827200e
commit f7709e7610
7 changed files with 184 additions and 12 deletions

View File

@ -276,11 +276,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
String discriminatorColumn, String discriminatorColumn,
NavigablePath entityPath) { NavigablePath entityPath) {
final EntityDiscriminatorMapping discriminatorMapping = entityMapping.getDiscriminatorMapping(); final EntityDiscriminatorMapping discriminatorMapping = entityMapping.getDiscriminatorMapping();
if ( discriminatorMapping == null ) { if ( discriminatorMapping == null || discriminatorColumn == null || !entityMapping.hasSubclasses() ) {
return null;
}
if ( discriminatorColumn == null ) {
return null; return null;
} }

View File

@ -178,10 +178,10 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
@Override @Override
public String getContainingTableExpression() { public String getContainingTableExpression() {
throw new UnsupportedOperationException(); // throw new UnsupportedOperationException();
// // this *should* only be used to create the sql-expression key, so just // // this *should* only be used to create the sql-expression key, so just
// // using the primary table expr should be fine // // using the primary table expr should be fine
// return entityDescriptor.getRootTableName(); return getEntityDescriptor().getMappedTableDetails().getTableName();
} }
@Override @Override

View File

@ -14,11 +14,14 @@ import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.spi.NavigablePath; import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -80,7 +83,15 @@ public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminato
SqlAstCreationState creationState) { SqlAstCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver(); final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver();
final TableReference tableReference = tableGroup.resolveTableReference( navigablePath, tableExpression ); final TableReference tableReference = tableGroup.resolveTableReference( navigablePath, tableExpression );
return expressionResolver.resolveSqlExpression( tableReference, this );
return expressionResolver.resolveSqlExpression(
createColumnReferenceKey(
tableGroup.getPrimaryTableReference(),
getSelectionExpression(),
jdbcMappingToUse
),
processingState -> new ColumnReference( tableReference, this )
);
} }
@Override @Override

View File

@ -59,7 +59,7 @@ public class ResultMementoEntityJpa implements ResultMementoEntity, FetchMemento
ResultSetMappingResolutionContext context) { ResultSetMappingResolutionContext context) {
final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping();
final BasicValuedFetchBuilder discriminatorFetchBuilder; final BasicValuedFetchBuilder discriminatorFetchBuilder;
if ( discriminatorMapping == null ) { if ( discriminatorMapping == null || !entityDescriptor.hasSubclasses() ) {
assert discriminatorMemento == null; assert discriminatorMemento == null;
discriminatorFetchBuilder = null; discriminatorFetchBuilder = null;
} }

View File

@ -13,7 +13,8 @@ import java.util.function.BiFunction;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.query.results.ResultsHelper; import org.hibernate.metamodel.mapping.DiscriminatorMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.spi.NavigablePath; import org.hibernate.spi.NavigablePath;
import org.hibernate.query.results.BasicValuedFetchBuilder; import org.hibernate.query.results.BasicValuedFetchBuilder;
import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.DomainResultCreationStateImpl;
@ -112,9 +113,17 @@ public class CompleteFetchBuilderBasicPart implements CompleteFetchBuilder, Basi
final int valuesArrayPosition = jdbcPositionToValuesArrayPosition( jdbcPosition ); final int valuesArrayPosition = jdbcPositionToValuesArrayPosition( jdbcPosition );
final JdbcMapping jdbcMapping;
if ( referencedModelPart instanceof DiscriminatorMapping ) {
jdbcMapping = ( (DiscriminatorMapping) referencedModelPart ).getUnderlyingJdbcMapping();
}
else {
jdbcMapping = referencedModelPart.getJdbcMapping();
}
// we just care about the registration here. The ModelPart will find it later // we just care about the registration here. The ModelPart will find it later
creationStateImpl.resolveSqlExpression( creationStateImpl.resolveSqlExpression(
createColumnReferenceKey( tableReference, referencedModelPart ), createColumnReferenceKey( tableReference, referencedModelPart.getSelectablePath(), jdbcMapping ),
processingState -> new ResultSetMappingSqlSelection( valuesArrayPosition, referencedModelPart ) processingState -> new ResultSetMappingSqlSelection( valuesArrayPosition, referencedModelPart )
); );

View File

@ -60,7 +60,7 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti
} }
else { else {
// discriminated // discriminated
assert discriminatorFetchBuilder != null; assert !entityDescriptor.hasSubclasses() || discriminatorFetchBuilder != null;
} }
} }

View File

@ -0,0 +1,156 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.query.resultmapping;
import java.util.List;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityResult;
import jakarta.persistence.FieldResult;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.NamedNativeQuery;
import jakarta.persistence.SqlResultSetMapping;
import jakarta.persistence.Table;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
@DomainModel( annotatedClasses = {
InheritanceTests.Parent.class,
InheritanceTests.Child.class
} )
@SessionFactory
@JiraKey("HHH-17102")
public class InheritanceTests {
@BeforeEach
public void prepareData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.save( new Child( 1L, "test", 123 ) );
}
);
}
@AfterEach
public void cleanUpData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete Child" ).executeUpdate();
}
);
}
@Test
public void testResultSetMappingForParentEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final List<Parent> children = session.createNamedQuery( "Parent", Parent.class ).getResultList();
assertEquals( 1, children.size() );
assertEquals( 1L, children.get( 0 ).id );
assertEquals( "test", children.get( 0 ).name );
assertInstanceOf( Child.class, children.get( 0 ) );
assertEquals( 123, ( (Child) children.get( 0 ) ).test );
}
);
}
@Test
public void testResultSetMappingForChildEntity(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final List<Child> children = session.createNamedQuery( "Child", Child.class ).getResultList();
assertEquals( 1, children.size() );
assertEquals( 1L, children.get( 0 ).id );
assertEquals( "test", children.get( 0 ).name );
assertEquals( 123, children.get( 0 ).test );
}
);
}
@SqlResultSetMapping(
name = "ParentResult",
entities = {
@EntityResult(
entityClass = Parent.class,
discriminatorColumn = "discr",
fields = {
@FieldResult(name = "id", column = "id"),
@FieldResult(name = "name", column = "name"),
@FieldResult(name = "test", column = "test")
}
)
}
)
@NamedNativeQuery(
name = "Parent",
query = "SELECT p.id as id, case when c.id is not null then 1 else 0 end as discr, p.name as name, c.test as test FROM parent p left join child c on p.id = c.id",
resultSetMapping = "ParentResult"
)
@Entity(name = "Parent")
@Table(name = "parent")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Parent {
@Id
@Column(name = "id")
Long id;
String name;
public Parent() {
}
public Parent(Long id, String name) {
this.id = id;
this.name = name;
}
}
@SqlResultSetMapping(
name = "ChildResult",
entities = {
@EntityResult(
entityClass = Child.class,
fields = {
@FieldResult(name = "id", column = "id"),
@FieldResult(name = "name", column = "name"),
@FieldResult(name = "test", column = "test")
}
)
}
)
@NamedNativeQuery(
name = "Child",
query = "SELECT c.id as id, 'test' as name, c.test FROM child c",
resultSetMapping = "ChildResult"
)
@Entity(name = "Child")
@Table(name = "child")
public static class Child extends Parent {
@Column(name = "test")
int test;
public Child() {
}
public Child(Long id, String name, int test) {
super( id, name );
this.test = test;
}
}
}