diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index b3f6c3eb54..2b4817c82f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -101,6 +101,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { private final boolean[] subclassTableSequentialSelect; private final boolean[] subclassTableIsLazyClosure; + private final boolean[] isInverseSubclassTable; + private final boolean[] isNullableSubclassTable; // subclass discrimination works by assigning particular // values to certain combinations of null primary key @@ -123,6 +125,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { private final int coreTableSpan; // only contains values for SecondaryTables, ie. not tables part of the "coreTableSpan" private final boolean[] isNullableTable; + private final boolean[] isInverseTable; //INITIALIZATION: @@ -235,20 +238,21 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { //Span of the tableNames directly mapped by this entity and super-classes, if any 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(); - while ( joinItr.hasNext() ) { + for ( int tableIndex = 0; joinItr.hasNext(); tableIndex++ ) { Join join = (Join) joinItr.next(); - isNullableTable[tableIndex++] = join.isOptional() || + isNullableTable[tableIndex] = join.isOptional() || creationContext.getSessionFactory() .getSessionFactoryOptions() .getJpaCompliance() .isJpaCacheComplianceEnabled(); - + isInverseTable[tableIndex] = join.isInverse(); Table table = join.getTable(); final String tableName = determineTableName( table, jdbcEnvironment ); @@ -285,6 +289,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { ArrayList isConcretes = new ArrayList(); ArrayList isDeferreds = new ArrayList(); ArrayList isLazies = new ArrayList(); + ArrayList isInverses = new ArrayList(); + ArrayList isNullables = new ArrayList(); keyColumns = new ArrayList(); tItr = persistentClass.getSubclassTableClosureIterator(); @@ -293,6 +299,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { isConcretes.add( persistentClass.isClassOrSuperclassTable( tab ) ); isDeferreds.add( Boolean.FALSE ); isLazies.add( Boolean.FALSE ); + isInverses.add( Boolean.FALSE ); + isNullables.add( Boolean.FALSE ); final String tableName = determineTableName( tab, jdbcEnvironment ); subclassTableNames.add( tableName ); String[] key = new String[idColumnSpan]; @@ -311,6 +319,13 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { isConcretes.add( persistentClass.isClassOrSuperclassTable( joinTable ) ); isDeferreds.add( join.isSequentialSelect() ); + isInverses.add( join.isInverse() ); + isNullables.add( + join.isOptional() || creationContext.getSessionFactory() + .getSessionFactoryOptions() + .getJpaCompliance() + .isJpaCacheComplianceEnabled() + ); isLazies.add( join.isLazy() ); String joinTableName = determineTableName( joinTable, jdbcEnvironment ); @@ -328,6 +343,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes ); subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds ); subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies ); + isInverseSubclassTable = ArrayHelper.toBooleanArray( isInverses ); + isNullableSubclassTable = ArrayHelper.toBooleanArray( isNullables ); constraintOrderedTableNames = new String[naturalOrderSubclassTableNameClosure.length]; constraintOrderedKeyColumnNames = new String[naturalOrderSubclassTableNameClosure.length][]; @@ -347,7 +364,6 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { * tableNames -> CLIENT, PERSON */ - tableSpan = naturalOrderTableNames.length; this.tableNames = reverse( naturalOrderTableNames, coreTableSpan ); tableKeyColumns = reverse( naturalOrderTableKeyColumns, coreTableSpan ); tableKeyColumnReaders = reverse( naturalOrderTableKeyColumnReaders, coreTableSpan ); @@ -374,6 +390,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { PersistentClass pc = persistentClass; int jk = coreTableSpan - 1; while ( pc != null ) { + isNullableTable[jk] = false; + isInverseTable[jk] = false; customSQLInsert[jk] = pc.getCustomSQLInsert(); insertCallable[jk] = customSQLInsert[jk] != null && pc.isCustomInsertCallable(); insertResultCheckStyles[jk] = pc.getCustomSQLInsertCheckStyle() == null @@ -404,6 +422,13 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { while ( joinItr.hasNext() ) { Join join = (Join) joinItr.next(); + isInverseTable[j] = join.isInverse(); + isNullableTable[j] = join.isOptional() + || creationContext.getSessionFactory() + .getSessionFactoryOptions() + .getJpaCompliance() + .isJpaCacheComplianceEnabled(); + customSQLInsert[j] = join.getCustomSQLInsert(); insertCallable[j] = customSQLInsert[j] != null && join.isCustomInsertCallable(); insertResultCheckStyles[j] = join.getCustomSQLInsertCheckStyle() == null @@ -723,11 +748,14 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { } } + @Override protected boolean isNullableTable(int j) { - if ( j < coreTableSpan ) { - return false; - } - return isNullableTable[j - coreTableSpan]; + return isNullableTable[j]; + } + + @Override + protected boolean isInverseTable(int j) { + return isInverseTable[j]; } protected boolean isSubclassTableSequentialSelect(int j) { @@ -744,6 +772,16 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { 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() { return discriminatorType; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index 1afbb6b852..4fc55dc357 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -258,13 +258,10 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) { hasDeferred = true; } - subclassTables.add( - join.getTable().getQualifiedName( - factory.getDialect(), - factory.getSettings().getDefaultCatalogName(), - factory.getSettings().getDefaultSchemaName() - ) - ); + + String joinTableName = determineTableName( join.getTable(), jdbcEnvironment ); + subclassTables.add( joinTableName ); + Iterator iter = join.getKey().getColumnIterator(); String[] keyCols = new String[join.getKey().getColumnSpan()]; int i = 0; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/JoinedSubclassAndSecondaryTable.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/JoinedSubclassAndSecondaryTable.java index 0ab9ef1640..0bde7c0a4c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/JoinedSubclassAndSecondaryTable.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/JoinedSubclassAndSecondaryTable.java @@ -14,14 +14,17 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** * @author Emmanuel Bernard */ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase { + @Test - public void testSecondaryTableAndJoined() throws Exception { + public void testSecondaryTableAndJoined() { Session s = openSession(); Transaction tx = s.beginTransaction(); SwimmingPool sp = new SwimmingPool(); @@ -29,7 +32,7 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase s.flush(); s.clear(); - long rowCount = getTableRowCount( s ); + long rowCount = getTableRowCount( s, "POOL_ADDRESS" ); assertEquals( "The address table is marked as optional. For null values no database row should be created", 0, @@ -37,7 +40,7 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase ); SwimmingPool sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() ); - assertEquals( sp.getAddress(), null ); + assertNull( sp.getAddress() ); PoolAddress address = new PoolAddress(); address.setAddress( "Park Avenue" ); @@ -47,24 +50,63 @@ public class JoinedSubclassAndSecondaryTable extends BaseCoreFunctionalTestCase s.clear(); sp2 = (SwimmingPool) s.get( SwimmingPool.class, sp.getId() ); - rowCount = getTableRowCount( s ); + rowCount = getTableRowCount( s, "POOL_ADDRESS" ); assertEquals( "Now we should have a row in the pool address table ", 1, rowCount ); - assertFalse( sp2.getAddress() == null ); + assertNotNull( sp2.getAddress() ); assertEquals( sp2.getAddress().getAddress(), "Park Avenue" ); tx.rollback(); 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 // Oracle returns Types.NUMERIC, which is mapped to BigDecimal; // 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 ) ); return ( ( Number ) retVal ).longValue(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/Pool.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/Pool.java index 1f8f35b572..d280edc2a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/Pool.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/inheritance/joined/Pool.java @@ -7,6 +7,10 @@ //$Id$ 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.Entity; import javax.persistence.GeneratedValue; @@ -14,14 +18,21 @@ import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.SecondaryTable; +import javax.persistence.SecondaryTables; /** * @author Emmanuel Bernard */ @Entity @Inheritance(strategy = InheritanceType.JOINED) -@SecondaryTable(name="POOL_ADDRESS") -@org.hibernate.annotations.Table(appliesTo="POOL_ADDRESS", optional=true) +@SecondaryTables({ + @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 { @Id @GeneratedValue private Integer id; @@ -29,6 +40,10 @@ public class Pool { @Embedded private PoolAddress address; + @Embedded + @AttributeOverride(name = "address", column = @Column(table = "POOL_ADDRESS_2")) + private PoolAddress secondaryAddress; + public PoolAddress getAddress() { return address; } @@ -37,6 +52,14 @@ public class Pool { this.address = address; } + public PoolAddress getSecondaryAddress() { + return secondaryAddress; + } + + public void setSecondaryAddress(PoolAddress secondaryAddress) { + this.secondaryAddress = secondaryAddress; + } + public Integer getId() { return id; }