HHH-12675 - Respect inverse property for JoinedSubclassEntityPersister

This commit is contained in:
Jan-Willem Gmelig Meyling 2018-06-10 15:26:50 +02:00 committed by Christian Beikov
parent 4bafeeecae
commit b25bfd79f4
4 changed files with 126 additions and 26 deletions

View File

@ -101,6 +101,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
private final boolean[] subclassTableSequentialSelect; private final boolean[] subclassTableSequentialSelect;
private final boolean[] subclassTableIsLazyClosure; private final boolean[] subclassTableIsLazyClosure;
private final boolean[] isInverseSubclassTable;
private final boolean[] isNullableSubclassTable;
// subclass discrimination works by assigning particular // subclass discrimination works by assigning particular
// values to certain combinations of null primary key // values to certain combinations of null primary key
@ -123,6 +125,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
private final int coreTableSpan; private final int coreTableSpan;
// only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan" // only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan"
private final boolean[] isNullableTable; private final boolean[] isNullableTable;
private final boolean[] isInverseTable;
//INITIALIZATION: //INITIALIZATION:
@ -235,20 +238,21 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
//Span of the tableNames directly mapped by this entity and super-classes, if any //Span of the tableNames directly mapped by this entity and super-classes, if any
coreTableSpan = tableNames.size(); coreTableSpan = tableNames.size();
tableSpan = persistentClass.getJoinClosureSpan() + coreTableSpan;
isNullableTable = new boolean[persistentClass.getJoinClosureSpan()]; isNullableTable = new boolean[tableSpan];
isInverseTable = new boolean[tableSpan];
int tableIndex = 0;
Iterator joinItr = persistentClass.getJoinClosureIterator(); Iterator joinItr = persistentClass.getJoinClosureIterator();
while ( joinItr.hasNext() ) { for ( int tableIndex = 0; joinItr.hasNext(); tableIndex++ ) {
Join join = (Join) joinItr.next(); Join join = (Join) joinItr.next();
isNullableTable[tableIndex++] = join.isOptional() || isNullableTable[tableIndex] = join.isOptional() ||
creationContext.getSessionFactory() creationContext.getSessionFactory()
.getSessionFactoryOptions() .getSessionFactoryOptions()
.getJpaCompliance() .getJpaCompliance()
.isJpaCacheComplianceEnabled(); .isJpaCacheComplianceEnabled();
isInverseTable[tableIndex] = join.isInverse();
Table table = join.getTable(); Table table = join.getTable();
final String tableName = determineTableName( table, jdbcEnvironment ); final String tableName = determineTableName( table, jdbcEnvironment );
@ -285,6 +289,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
ArrayList<Boolean> isConcretes = new ArrayList<Boolean>(); ArrayList<Boolean> isConcretes = new ArrayList<Boolean>();
ArrayList<Boolean> isDeferreds = new ArrayList<Boolean>(); ArrayList<Boolean> isDeferreds = new ArrayList<Boolean>();
ArrayList<Boolean> isLazies = new ArrayList<Boolean>(); ArrayList<Boolean> isLazies = new ArrayList<Boolean>();
ArrayList<Boolean> isInverses = new ArrayList<Boolean>();
ArrayList<Boolean> isNullables = new ArrayList<Boolean>();
keyColumns = new ArrayList<String[]>(); keyColumns = new ArrayList<String[]>();
tItr = persistentClass.getSubclassTableClosureIterator(); tItr = persistentClass.getSubclassTableClosureIterator();
@ -293,6 +299,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
isConcretes.add( persistentClass.isClassOrSuperclassTable( tab ) ); isConcretes.add( persistentClass.isClassOrSuperclassTable( tab ) );
isDeferreds.add( Boolean.FALSE ); isDeferreds.add( Boolean.FALSE );
isLazies.add( Boolean.FALSE ); isLazies.add( Boolean.FALSE );
isInverses.add( Boolean.FALSE );
isNullables.add( Boolean.FALSE );
final String tableName = determineTableName( tab, jdbcEnvironment ); final String tableName = determineTableName( tab, jdbcEnvironment );
subclassTableNames.add( tableName ); subclassTableNames.add( tableName );
String[] key = new String[idColumnSpan]; String[] key = new String[idColumnSpan];
@ -311,6 +319,13 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
isConcretes.add( persistentClass.isClassOrSuperclassTable( joinTable ) ); isConcretes.add( persistentClass.isClassOrSuperclassTable( joinTable ) );
isDeferreds.add( join.isSequentialSelect() ); isDeferreds.add( join.isSequentialSelect() );
isInverses.add( join.isInverse() );
isNullables.add(
join.isOptional() || creationContext.getSessionFactory()
.getSessionFactoryOptions()
.getJpaCompliance()
.isJpaCacheComplianceEnabled()
);
isLazies.add( join.isLazy() ); isLazies.add( join.isLazy() );
String joinTableName = determineTableName( joinTable, jdbcEnvironment ); String joinTableName = determineTableName( joinTable, jdbcEnvironment );
@ -328,6 +343,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes ); isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes );
subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds ); subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies ); subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies );
isInverseSubclassTable = ArrayHelper.toBooleanArray( isInverses );
isNullableSubclassTable = ArrayHelper.toBooleanArray( isNullables );
constraintOrderedTableNames = new String[naturalOrderSubclassTableNameClosure.length]; constraintOrderedTableNames = new String[naturalOrderSubclassTableNameClosure.length];
constraintOrderedKeyColumnNames = new String[naturalOrderSubclassTableNameClosure.length][]; constraintOrderedKeyColumnNames = new String[naturalOrderSubclassTableNameClosure.length][];
@ -347,7 +364,6 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
* tableNames -> CLIENT, PERSON * tableNames -> CLIENT, PERSON
*/ */
tableSpan = naturalOrderTableNames.length;
this.tableNames = reverse( naturalOrderTableNames, coreTableSpan ); this.tableNames = reverse( naturalOrderTableNames, coreTableSpan );
tableKeyColumns = reverse( naturalOrderTableKeyColumns, coreTableSpan ); tableKeyColumns = reverse( naturalOrderTableKeyColumns, coreTableSpan );
tableKeyColumnReaders = reverse( naturalOrderTableKeyColumnReaders, coreTableSpan ); tableKeyColumnReaders = reverse( naturalOrderTableKeyColumnReaders, coreTableSpan );
@ -374,6 +390,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
PersistentClass pc = persistentClass; PersistentClass pc = persistentClass;
int jk = coreTableSpan - 1; int jk = coreTableSpan - 1;
while ( pc != null ) { while ( pc != null ) {
isNullableTable[jk] = false;
isInverseTable[jk] = false;
customSQLInsert[jk] = pc.getCustomSQLInsert(); customSQLInsert[jk] = pc.getCustomSQLInsert();
insertCallable[jk] = customSQLInsert[jk] != null && pc.isCustomInsertCallable(); insertCallable[jk] = customSQLInsert[jk] != null && pc.isCustomInsertCallable();
insertResultCheckStyles[jk] = pc.getCustomSQLInsertCheckStyle() == null insertResultCheckStyles[jk] = pc.getCustomSQLInsertCheckStyle() == null
@ -404,6 +422,13 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
while ( joinItr.hasNext() ) { while ( joinItr.hasNext() ) {
Join join = (Join) joinItr.next(); Join join = (Join) joinItr.next();
isInverseTable[j] = join.isInverse();
isNullableTable[j] = join.isOptional()
|| creationContext.getSessionFactory()
.getSessionFactoryOptions()
.getJpaCompliance()
.isJpaCacheComplianceEnabled();
customSQLInsert[j] = join.getCustomSQLInsert(); customSQLInsert[j] = join.getCustomSQLInsert();
insertCallable[j] = customSQLInsert[j] != null && join.isCustomInsertCallable(); insertCallable[j] = customSQLInsert[j] != null && join.isCustomInsertCallable();
insertResultCheckStyles[j] = join.getCustomSQLInsertCheckStyle() == null insertResultCheckStyles[j] = join.getCustomSQLInsertCheckStyle() == null
@ -723,11 +748,14 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
} }
} }
@Override
protected boolean isNullableTable(int j) { protected boolean isNullableTable(int j) {
if ( j < coreTableSpan ) { return isNullableTable[j];
return false; }
}
return isNullableTable[j - coreTableSpan]; @Override
protected boolean isInverseTable(int j) {
return isInverseTable[j];
} }
protected boolean isSubclassTableSequentialSelect(int j) { protected boolean isSubclassTableSequentialSelect(int j) {
@ -744,6 +772,16 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
return subclassTableNameClosure[subclassPropertyTableNumberClosure[i]]; return subclassTableNameClosure[subclassPropertyTableNumberClosure[i]];
} }
@Override
protected boolean isInverseSubclassTable(int j) {
return isInverseSubclassTable[j];
}
@Override
protected boolean isNullableSubclassTable(int j) {
return isNullableSubclassTable[j];
}
public Type getDiscriminatorType() { public Type getDiscriminatorType() {
return discriminatorType; return discriminatorType;
} }

View File

@ -258,13 +258,10 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) { if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) {
hasDeferred = true; hasDeferred = true;
} }
subclassTables.add(
join.getTable().getQualifiedName( String joinTableName = determineTableName( join.getTable(), jdbcEnvironment );
factory.getDialect(), subclassTables.add( joinTableName );
factory.getSettings().getDefaultCatalogName(),
factory.getSettings().getDefaultSchemaName()
)
);
Iterator iter = join.getKey().getColumnIterator(); Iterator iter = join.getKey().getColumnIterator();
String[] keyCols = new String[join.getKey().getColumnSpan()]; String[] keyCols = new String[join.getKey().getColumnSpan()];
int i = 0; int i = 0;

View File

@ -14,14 +14,17 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase { public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase {
@Test @Test
public void testSecondaryTableAndJoined() throws Exception { public void testSecondaryTableAndJoined() {
Session s = openSession(); Session s = openSession();
Transaction tx = s.beginTransaction(); Transaction tx = s.beginTransaction();
SwimmingPool sp = new SwimmingPool(); SwimmingPool sp = new SwimmingPool();
@ -29,7 +32,7 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase
s.flush(); s.flush();
s.clear(); s.clear();
long rowCount = getTableRowCount( s ); long rowCount = getTableRowCount( s, "POOL_ADDRESS" );
assertEquals( assertEquals(
"The address table is marked as optional. For null values no database row should be created", "The address table is marked as optional. For null values no database row should be created",
0, 0,
@ -37,7 +40,7 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase
); );
SwimmingPool sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() ); SwimmingPool sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
assertEquals( sp.getAddress(), null ); assertNull( sp.getAddress() );
PoolAddress address = new PoolAddress(); PoolAddress address = new PoolAddress();
address.setAddress( "Park Avenue" ); address.setAddress( "Park Avenue" );
@ -47,24 +50,63 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase
s.clear(); s.clear();
sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() ); sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
rowCount = getTableRowCount( s ); rowCount = getTableRowCount( s, "POOL_ADDRESS" );
assertEquals( assertEquals(
"Now we should have a row in the pool address table ", "Now we should have a row in the pool address table ",
1, 1,
rowCount rowCount
); );
assertFalse( sp2.getAddress() == null ); assertNotNull( sp2.getAddress() );
assertEquals( sp2.getAddress().getAddress(), "Park Avenue" ); assertEquals( sp2.getAddress().getAddress(), "Park Avenue" );
tx.rollback(); tx.rollback();
s.close(); s.close();
} }
private long getTableRowCount(Session s) { @Test
public void testSecondaryTableAndJoinedInverse() throws Exception {
Session s = openSession();
Transaction tx = s.beginTransaction();
SwimmingPool sp = new SwimmingPool();
s.persist( sp );
s.flush();
s.clear();
long rowCount = getTableRowCount( s, "POOL_ADDRESS_2" );
assertEquals(
"The address table is marked as optional. For null values no database row should be created",
0,
rowCount
);
SwimmingPool sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
assertNull( sp.getSecondaryAddress() );
PoolAddress address = new PoolAddress();
address.setAddress( "Park Avenue" );
sp2.setSecondaryAddress( address );
s.flush();
s.clear();
sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() );
rowCount = getTableRowCount( s, "POOL_ADDRESS_2" );
assertEquals(
"Now we should have a row in the pool address table ",
0,
rowCount
);
assertNull( sp2.getSecondaryAddress() );
tx.rollback();
s.close();
}
private long getTableRowCount(Session s, String tableName) {
// the type returned for count(*) in a native query depends on the dialect // the type returned for count(*) in a native query depends on the dialect
// Oracle returns Types.NUMERIC, which is mapped to BigDecimal; // Oracle returns Types.NUMERIC, which is mapped to BigDecimal;
// H2 returns Types.BIGINT, which is mapped to BigInteger; // H2 returns Types.BIGINT, which is mapped to BigInteger;
Object retVal = s.createSQLQuery( "select count(*) from POOL_ADDRESS" ).uniqueResult(); Object retVal = s.createSQLQuery( "select count(*) from " + tableName ).uniqueResult();
assertTrue( Number.class.isInstance( retVal ) ); assertTrue( Number.class.isInstance( retVal ) );
return ( ( Number ) retVal ).longValue(); return ( ( Number ) retVal ).longValue();
} }

View File

@ -7,6 +7,10 @@
//$Id$ //$Id$
package org.hibernate.test.annotations.inheritance.joined; package org.hibernate.test.annotations.inheritance.joined;
import org.hibernate.annotations.Tables;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Embedded; import javax.persistence.Embedded;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
@ -14,14 +18,21 @@ import javax.persistence.Id;
import javax.persistence.Inheritance; import javax.persistence.Inheritance;
import javax.persistence.InheritanceType; import javax.persistence.InheritanceType;
import javax.persistence.SecondaryTable; import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
/** /**
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
@Entity @Entity
@Inheritance(strategy = InheritanceType.JOINED) @Inheritance(strategy = InheritanceType.JOINED)
@SecondaryTable(name="POOL_ADDRESS") @SecondaryTables({
@org.hibernate.annotations.Table(appliesTo="POOL_ADDRESS", optional=true) @SecondaryTable(name="POOL_ADDRESS"),
@SecondaryTable(name="POOL_ADDRESS_2")
})
@Tables({
@org.hibernate.annotations.Table(appliesTo="POOL_ADDRESS", optional=true),
@org.hibernate.annotations.Table(appliesTo="POOL_ADDRESS_2", optional=true, inverse = true)
})
public class Pool { public class Pool {
@Id @GeneratedValue @Id @GeneratedValue
private Integer id; private Integer id;
@ -29,6 +40,10 @@ public class Pool {
@Embedded @Embedded
private PoolAddress address; private PoolAddress address;
@Embedded
@AttributeOverride(name = "address", column = @Column(table = "POOL_ADDRESS_2"))
private PoolAddress secondaryAddress;
public PoolAddress getAddress() { public PoolAddress getAddress() {
return address; return address;
} }
@ -37,6 +52,14 @@ public class Pool {
this.address = address; this.address = address;
} }
public PoolAddress getSecondaryAddress() {
return secondaryAddress;
}
public void setSecondaryAddress(PoolAddress secondaryAddress) {
this.secondaryAddress = secondaryAddress;
}
public Integer getId() { public Integer getId() {
return id; return id;
} }