HHH-13725 - Implement ManyToOne with Join Table associations support

This commit is contained in:
Andrea Boriero 2019-11-15 15:33:31 +00:00 committed by Steve Ebersole
parent 36bf9f9dfe
commit ed49f6abcf
3 changed files with 100 additions and 30 deletions

View File

@ -25,6 +25,7 @@ import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.ColumnReference;
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.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.internal.domain.basic.BasicResult; import org.hibernate.sql.results.internal.domain.basic.BasicResult;
@ -63,14 +64,16 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor {
DomainResultCreationState creationState) { DomainResultCreationState creationState) {
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver();
final TableReference keyTableKeyReference = getKeyTableReference( tableGroup, tableGroup );
final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection(
sqlExpressionResolver.resolveSqlExpression( sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( SqlExpressionResolver.createColumnReferenceKey(
tableGroup.getPrimaryTableReference(), keyTableKeyReference,
keyColumnExpression keyColumnExpression
), ),
s -> new ColumnReference( s -> new ColumnReference(
tableGroup.getPrimaryTableReference().getIdentificationVariable(), keyTableKeyReference,
keyColumnExpression, keyColumnExpression,
jdbcMapping, jdbcMapping,
creationState.getSqlAstCreationState().getCreationContext().getSessionFactory() creationState.getSqlAstCreationState().getCreationContext().getSessionFactory()
@ -95,25 +98,27 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor {
JoinType joinType, JoinType joinType,
SqlExpressionResolver sqlExpressionResolver, SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext) { SqlAstCreationContext creationContext) {
final TableReference tableReference = lhs.resolveTableReference( targetColumnContainingTable ); final TableReference targetTableReference = lhs.resolveTableReference( targetColumnContainingTable );
final ColumnReference targetReference = (ColumnReference) sqlExpressionResolver.resolveSqlExpression( final ColumnReference targetReference = (ColumnReference) sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( tableReference, keyColumnExpression ), SqlExpressionResolver.createColumnReferenceKey( targetTableReference, keyColumnExpression ),
s -> new ColumnReference( s -> new ColumnReference(
tableReference.getIdentificationVariable(), targetTableReference.getIdentificationVariable(),
targetColumnExpression, targetColumnExpression,
jdbcMapping, jdbcMapping,
creationContext.getSessionFactory() creationContext.getSessionFactory()
) )
); );
final TableReference keyTableKeyReference = getKeyTableReference( lhs, tableGroup );
final ColumnReference keyReference = (ColumnReference) sqlExpressionResolver.resolveSqlExpression( final ColumnReference keyReference = (ColumnReference) sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( SqlExpressionResolver.createColumnReferenceKey(
tableGroup.getPrimaryTableReference(), keyTableKeyReference,
keyColumnExpression keyColumnExpression
), ),
s -> new ColumnReference( s -> new ColumnReference(
tableGroup.getPrimaryTableReference().getIdentificationVariable(), keyTableKeyReference.getIdentificationVariable(),
keyColumnExpression, keyColumnExpression,
jdbcMapping, jdbcMapping,
creationContext.getSessionFactory() creationContext.getSessionFactory()
@ -127,6 +132,15 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor {
); );
} }
protected TableReference getKeyTableReference(TableGroup lhs, TableGroup tableGroup) {
for ( TableReferenceJoin tableJoin : lhs.getTableReferenceJoins() ) {
if ( tableJoin.getJoinedTableReference().getTableExpression().equals( keyColumnContainingTable ) ) {
return tableJoin.getJoinedTableReference();
}
}
return tableGroup.getPrimaryTableReference();
}
@Override @Override
public JavaTypeDescriptor getJavaTypeDescriptor() { public JavaTypeDescriptor getJavaTypeDescriptor() {
return jdbcMapping.getJavaTypeDescriptor(); return jdbcMapping.getJavaTypeDescriptor();
@ -144,7 +158,13 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor {
@Override @Override
public void visitColumnMappings(FkColumnMappingConsumer consumer) { public void visitColumnMappings(FkColumnMappingConsumer consumer) {
consumer.consume( keyColumnContainingTable, keyColumnExpression, targetColumnContainingTable, targetColumnExpression, jdbcMapping ); consumer.consume(
keyColumnContainingTable,
keyColumnExpression,
targetColumnContainingTable,
targetColumnExpression,
jdbcMapping
);
} }
@Override @Override

View File

@ -10,10 +10,10 @@ import java.util.Calendar;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.testing.orm.domain.gambit.BasicEntity;
import org.hibernate.testing.orm.domain.gambit.EntityWithManyToOneJoinTable; import org.hibernate.testing.orm.domain.gambit.EntityWithManyToOneJoinTable;
import org.hibernate.testing.orm.domain.gambit.SimpleEntity; import org.hibernate.testing.orm.domain.gambit.SimpleEntity;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
@ -32,7 +32,8 @@ import static org.hamcrest.MatcherAssert.assertThat;
@DomainModel( @DomainModel(
annotatedClasses = { annotatedClasses = {
EntityWithManyToOneJoinTable.class, EntityWithManyToOneJoinTable.class,
SimpleEntity.class SimpleEntity.class,
BasicEntity.class
} }
) )
@ServiceRegistry @ServiceRegistry
@ -75,7 +76,6 @@ public class EntityWithManyToOneJoinTableTest {
} }
@Test @Test
@FailureExpected
public void testSaveInDifferentTransactions(SessionFactoryScope scope) { public void testSaveInDifferentTransactions(SessionFactoryScope scope) {
EntityWithManyToOneJoinTable entity = new EntityWithManyToOneJoinTable( 3, "second", Integer.MAX_VALUE ); EntityWithManyToOneJoinTable entity = new EntityWithManyToOneJoinTable( 3, "second", Integer.MAX_VALUE );
@ -83,7 +83,7 @@ public class EntityWithManyToOneJoinTableTest {
4, 4,
Calendar.getInstance().getTime(), Calendar.getInstance().getTime(),
Calendar.getInstance().toInstant(), Calendar.getInstance().toInstant(),
Integer.MAX_VALUE -1 , Integer.MAX_VALUE - 1,
Long.MAX_VALUE, Long.MAX_VALUE,
null null
); );
@ -110,22 +110,25 @@ public class EntityWithManyToOneJoinTableTest {
} }
@Test @Test
@FailureExpected
public void testHqlSelect(SessionFactoryScope scope) { public void testHqlSelect(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics(); final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear(); statistics.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
final EntityWithManyToOneJoinTable result = session.createQuery( final EntityWithManyToOneJoinTable result = session.createQuery(
"select e from EntityWithManyToOneJoinTable e where e.id = 2", "select e from EntityWithManyToOneJoinTable e where e.id = 1",
EntityWithManyToOneJoinTable.class EntityWithManyToOneJoinTable.class
).uniqueResult(); ).uniqueResult();
assertThat( result, notNullValue() ); assertThat( result, notNullValue() );
assertThat( result.getId(), is( 2 ) ); assertThat( result.getId(), is( 1 ) );
assertThat( result.getName(), is( "first" ) ); assertThat( result.getName(), is( "first" ) );
assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); assertThat( statistics.getPrepareStatementCount(), is( 2L ) );
assertThat( result.getOther().getSomeInteger(), is( Integer.MAX_VALUE ) );
assertThat( statistics.getPrepareStatementCount(), is( 2L ) );
} }
); );
} }
@ -140,6 +143,7 @@ public class EntityWithManyToOneJoinTableTest {
"select e.name from EntityWithManyToOneJoinTable e where e.other.id = 2", "select e.name from EntityWithManyToOneJoinTable e where e.other.id = 2",
String.class String.class
).uniqueResult(); ).uniqueResult();
assertThat( value, equalTo( "first" ) ); assertThat( value, equalTo( "first" ) );
assertThat( statistics.getPrepareStatementCount(), is( 1L ) ); assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
@ -148,29 +152,63 @@ public class EntityWithManyToOneJoinTableTest {
} }
@Test @Test
@FailureExpected
public void testHqlSelectWithJoin(SessionFactoryScope scope) { public void testHqlSelectWithJoin(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
final String value = session.createQuery( final EntityWithManyToOneJoinTable result = session.createQuery(
"select from EntityWithManyToOneJoinTable e where e.id = 2", "select e from EntityWithManyToOneJoinTable e join e.other where e.id = 1",
String.class EntityWithManyToOneJoinTable.class
).uniqueResult(); ).uniqueResult();
assertThat( value, equalTo( "first" ) ); assertThat( result, notNullValue() );
assertThat( result.getId(), is( 1 ) );
assertThat( result.getName(), is( "first" ) );
assertThat( statistics.getPrepareStatementCount(), is( 2L ) );
assertThat( result.getOther().getId(), is( 2 ) );
assertThat( result.getOther().getSomeInteger(), is( Integer.MAX_VALUE ) );
assertThat( statistics.getPrepareStatementCount(), is( 2L ) );
}
);
}
@Test
public void testHqlSelectWithJoinFetch(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
scope.inTransaction(
session -> {
final EntityWithManyToOneJoinTable result = session.createQuery(
"select e from EntityWithManyToOneJoinTable e join fetch e.other where e.id = 1",
EntityWithManyToOneJoinTable.class
).uniqueResult();
assertThat( result, notNullValue() );
assertThat( result.getId(), is( 1 ) );
assertThat( result.getName(), is( "first" ) );
assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
assertThat( result.getOther().getId(), is( 2 ) );
assertThat( result.getOther().getSomeInteger(), is( Integer.MAX_VALUE ) );
assertThat( statistics.getPrepareStatementCount(), is( 1L ) );
} }
); );
} }
@Test @Test
@FailureExpected
public void testUpdate(SessionFactoryScope scope) { public void testUpdate(SessionFactoryScope scope) {
EntityWithManyToOneJoinTable entity = new EntityWithManyToOneJoinTable( 1, "first", Integer.MAX_VALUE ); EntityWithManyToOneJoinTable entity = new EntityWithManyToOneJoinTable( 2, "second", Integer.MAX_VALUE );
SimpleEntity other = new SimpleEntity( SimpleEntity other = new SimpleEntity(
2, 4,
Calendar.getInstance().getTime(), Calendar.getInstance().getTime(),
null, null,
Integer.MAX_VALUE, 100,
Long.MAX_VALUE, Long.MAX_VALUE,
null null
); );
@ -183,17 +221,17 @@ public class EntityWithManyToOneJoinTableTest {
} ); } );
SimpleEntity anOther = new SimpleEntity( SimpleEntity anOther = new SimpleEntity(
3, 5,
Calendar.getInstance().getTime(), Calendar.getInstance().getTime(),
null, null,
Integer.MIN_VALUE, Integer.MIN_VALUE + 5,
Long.MIN_VALUE, Long.MIN_VALUE,
null null
); );
scope.inTransaction( scope.inTransaction(
session -> { session -> {
final EntityWithManyToOneJoinTable loaded = session.get( EntityWithManyToOneJoinTable.class, 1 ); final EntityWithManyToOneJoinTable loaded = session.get( EntityWithManyToOneJoinTable.class, 2 );
assert loaded != null; assert loaded != null;
session.save( anOther ); session.save( anOther );
loaded.setOther( anOther ); loaded.setOther( anOther );
@ -202,10 +240,10 @@ public class EntityWithManyToOneJoinTableTest {
scope.inTransaction( scope.inTransaction(
session -> { session -> {
final EntityWithManyToOneJoinTable loaded = session.get( EntityWithManyToOneJoinTable.class, 1 ); final EntityWithManyToOneJoinTable loaded = session.get( EntityWithManyToOneJoinTable.class, 2 );
assertThat( loaded.getOther(), notNullValue() ); assertThat( loaded.getOther(), notNullValue() );
assertThat( loaded.getOther().getId(), equalTo( 3 ) ); assertThat( loaded.getOther().getId(), equalTo( 5 ) );
} }
); );
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.testing.orm.domain.gambit; package org.hibernate.testing.orm.domain.gambit;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.JoinTable; import javax.persistence.JoinTable;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
@ -22,6 +23,7 @@ public class EntityWithManyToOneJoinTable {
private String name; private String name;
private SimpleEntity other; private SimpleEntity other;
private Integer someInteger; private Integer someInteger;
private BasicEntity lazyOther;
public EntityWithManyToOneJoinTable() { public EntityWithManyToOneJoinTable() {
} }
@ -59,6 +61,16 @@ public class EntityWithManyToOneJoinTable {
this.other = other; this.other = other;
} }
@ManyToOne(fetch = FetchType.LAZY)
@JoinTable(name = "ENTITY_ANOTHER")
public BasicEntity getLazyOther() {
return lazyOther;
}
public void setLazyOther(BasicEntity lazyOther) {
this.lazyOther = lazyOther;
}
public Integer getSomeInteger() { public Integer getSomeInteger() {
return someInteger; return someInteger;
} }