HHH-13725 - Implement ManyToOne with Join Table associations support
This commit is contained in:
parent
36bf9f9dfe
commit
ed49f6abcf
|
@ -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
|
||||||
|
|
|
@ -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 ) );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue