Pages

Table per subclass

Table per subclass

By this strategy, you can represent inheritance relationship as a relational foreign key associations. Every class (including abstract class) has its own database table. table contains coulumns only for properties declared by subclass itself and  primary key of the sub class table is also a foreign key of the superclass table.

Again, we follow the same example which we referred in my previous post with some modification, we have added the identifier properties to both classes CreditCardDetails and BankAccountDetails and marked superclass as abstract.

public abstract class CreditCardHolder {
private Long creditCardHolderId;
private String owner;
     //generate getters and setters method for above properties
}
public class CreditCardDetails extends CreditCardHolder {
private Long creditCardId;
private String number;
private String expMonth;
private String expYear;
        //generate getters and setters method for above properties
}
public class BankAccountDetails extends CreditCardHolder {
private Long bankAccountId;
private String account;
private String bankName;
       //generate getters and setters method for above properties
}

also we have modified the CreditCardHolder.hbm.xml file. Now, there is no need to mentioned discriminator   column in the database table.

<hibernate-mapping package="table.per.subclass">
<class name="CreditCardHolder" dynamic-insert="true" dynamic-update="true">
<id name="creditCardHolderId" column="CREDIT_CARD_HOLDER_ID" type="long">
<generator class="increment"/>
</id>
<property name="owner" column="OWNER"/>
<joined-subclass name="CreditCardDetails" table="CREDIT_CARD_DETAILS">
      <!-- The key element is mandatory to share the foreign key relationship with superclass primary key -->
<key column="CREDIT_CARD_ID"/>
<property name="number" column="CREDIT_CARD_NUMBER"/>
<property name="expMonth" column="EXP_MONTH"/>
<property name="expYear" column="EXP_YEAR"/>
</joined-subclass>
<joined-subclass name="BankAccountDetails" table="BANK_ACCOUNT_DETAILS">
<key column="BANK_ACCOUNT_ID"/>
<property name="account" column="ACCOUNT_TYPE"/>
<property name="bankName" column="BANK_NAME"/>
</joined-subclass>
</class>
</hibernate-mapping>

if you run the following code, 
public class HibernateTest {
private final static Logger LOGGER = Logger.getLogger(HibernateTest.class); 
/**
* @param args
*/
public static void main(String[] args) {
LOGGER.info("starting of main method");
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction txn = session.beginTransaction();
CreditCardDetails creditCard = new CreditCardDetails();
creditCard.setNumber("3423243234342");
creditCard.setOwner("Sachin Sharma");
creditCard.setExpMonth("10/18");
creditCard.setExpYear("2018");
BankAccountDetails bankAccountDetails = new BankAccountDetails();
bankAccountDetails.setAccount("saving");
bankAccountDetails.setBankName("SBI");
bankAccountDetails.setOwner("Sachin Sharma");
session.saveOrUpdate(creditCard);
session.saveOrUpdate(bankAccountDetails);

txn.commit();
session.close();
HibernateUtil.shutDown();
}
}
Now, value of the superclass properties are saved in the superclass table and value of the subclass properties are saved in the subclass tables. two row are linked together by their shared primary key value.

subclass instances should be retrieve by joining the sublcass table with superclass table.\
The advantage of this strategy is , the schema is normalized.

See the hibernate generated SQL statements as follows :

11:55:05,277 INFO HibernateTest:20 - starting of main method
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Environment <clinit>
INFO: Hibernate 3.0rc1
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Environment <clinit>
INFO: hibernate.properties not found
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Environment <clinit>
INFO: using CGLIB reflection optimizer
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Environment <clinit>
INFO: using JDK 1.4 java.sql.Timestamp handling
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration configure
INFO: configuring from resource: /hibernate.cfg.xml
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration getConfigurationInputStream
INFO: Configuration resource: /hibernate.cfg.xml
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration addResource
INFO: Mapping resource: table/per/subclass/CreditCardHolder.hbm.xml
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.HbmBinder bindRootPersistentClassCommonValues
INFO: Mapping class: table.per.subclass.CreditCardHolder -> CREDITCARDHOLDER
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.HbmBinder bindJoinedSubclass
INFO: Mapping joined-subclass: table.per.subclass.CreditCardDetails -> CE_CREDIT_CARD_DETAILS
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.HbmBinder bindJoinedSubclass
INFO: Mapping joined-subclass: table.per.subclass.BankAccountDetails -> CE_BANK_ACCOUNT_DETAILS
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration doConfigure
INFO: Configured SessionFactory: null
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing extends queue
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing collection mappings
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing association property references
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing foreign key constraints
Oct 16, 2012 11:55:05 AM org.hibernate.dialect.Dialect <init>
INFO: Using dialect: org.hibernate.dialect.MySQLDialect
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Maximum outer join fetch depth: 2
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default batch fetch size: 1
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Generate SQL with comments: disabled
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL updates by primary key: disabled
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.SettingsFactory createQueryTranslatorFactory
INFO: Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
Oct 16, 2012 11:55:05 AM org.hibernate.hql.ast.ASTQueryTranslatorFactory <init>
INFO: Using ASTQueryTranslatorFactory
Oct 16, 2012 11:55:05 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query language substitutions: {}
Oct 16, 2012 11:55:05 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: C3P0 using driver: null at URL: jdbc:mysql://localhost:3306/HibernateTest
Oct 16, 2012 11:55:05 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: Connection properties: {autocommit=true, password=****, user=root}
Oct 16, 2012 11:55:05 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: autocommit mode: true
Oct 16, 2012 11:55:05 AM org.hibernate.connection.C3P0ConnectionProvider configure
WARNING: No JDBC Driver class was specified by property hibernate.connection.driver_class
11:55:05,562 INFO MLog:80 - MLog clients using log4j logging.
11:55:05,806 INFO C3P0Registry:204 - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
11:55:05,863 INFO AbstractPoolBackedDataSource:462 - Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@b0203cb4 [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@8e8a79c7 [ acquireIncrement -> 1, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, debugUnreturnedConnectionStackTraces -> false, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1bs1yqt8qsxalh4mtgoze|e40317, idleConnectionTestPeriod -> 3000, initialPoolSize -> 5, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 300, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 100, maxStatementsPerConnection -> 0, minPoolSize -> 5, nestedDataSource -> com.mchange.v2.c3p0.DriverManagerDataSource@af526d1c [ description -> null, driverClass -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qsxalh4mtgoze|1d4b0e9, jdbcUrl -> jdbc:mysql://localhost:3306/HibernateTest, properties -> {autocommit=true, password=******, user=******} ], preferredTestQuery -> null, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false; userOverrides: {} ], dataSourceName -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qsxalh4mtgoze|1be098f, numHelperThreads -> 3 ]
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch size: 15
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch updates for versioned data: disabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Scrollable result sets: enabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC3 getGeneratedKeys(): enabled
Oct 16, 2012 11:55:06 AM org.hibernate.transaction.TransactionFactoryFactory buildTransactionFactory
INFO: Using default transaction strategy (direct JDBC transactions)
Oct 16, 2012 11:55:06 AM org.hibernate.transaction.TransactionManagerLookupFactory getTransactionManagerLookup
INFO: No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic flush during beforeCompletion(): disabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic session close at end of transaction: disabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory createCacheProvider
INFO: Cache provider: org.hibernate.cache.EhCacheProvider
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Second-level cache: enabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Optimize cache for minimal puts: disabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Structured second-level cache entries: enabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query cache: disabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Echoing all SQL to stdout
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Statistics: disabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Deleted entity synthetic identifier rollback: disabled
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default entity-mode: pojo
Oct 16, 2012 11:55:06 AM org.hibernate.impl.SessionFactoryImpl <init>
INFO: building session factory
Oct 16, 2012 11:55:06 AM net.sf.ehcache.config.Configurator configure
WARNING: No configuration found. Configuring ehcache from ehcache-failsafe.xml found in the classpath: jar:file:/D:/Anuj/Jars/HibernateLib/ehcache-1.1.jar!/ehcache-failsafe.xml
Oct 16, 2012 11:55:06 AM org.hibernate.impl.SessionFactoryObjectFactory addInstance
INFO: Not binding factory to JNDI, no JNDI name configured
Oct 16, 2012 11:55:06 AM org.hibernate.dialect.Dialect <init>
INFO: Using dialect: org.hibernate.dialect.MySQLDialect
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing extends queue
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing collection mappings
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing association property references
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing foreign key constraints
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing extends queue
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing collection mappings
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing association property references
Oct 16, 2012 11:55:06 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing foreign key constraints
Oct 16, 2012 11:55:06 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: Running hbm2ddl schema export
Oct 16, 2012 11:55:06 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: exporting generated schema to database
Oct 16, 2012 11:55:06 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: C3P0 using driver: null at URL: jdbc:mysql://localhost:3306/HibernateTest
Oct 16, 2012 11:55:06 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: Connection properties: {autocommit=true, password=****, user=root}
Oct 16, 2012 11:55:06 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: autocommit mode: true
Oct 16, 2012 11:55:06 AM org.hibernate.connection.C3P0ConnectionProvider configure
WARNING: No JDBC Driver class was specified by property hibernate.connection.driver_class
11:55:06,515 INFO AbstractPoolBackedDataSource:462 - Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@d956e602 [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@ae930154 [ acquireIncrement -> 1, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, debugUnreturnedConnectionStackTraces -> false, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1bs1yqt8qsxalh4mtgoze|1cb63fd, idleConnectionTestPeriod -> 3000, initialPoolSize -> 5, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 300, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 100, maxStatementsPerConnection -> 0, minPoolSize -> 5, nestedDataSource -> com.mchange.v2.c3p0.DriverManagerDataSource@d962f213 [ description -> null, driverClass -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qsxalh4mtgoze|988edd, jdbcUrl -> jdbc:mysql://localhost:3306/HibernateTest, properties -> {autocommit=true, password=******, user=******} ], preferredTestQuery -> null, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false; userOverrides: {} ], dataSourceName -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qsxalh4mtgoze|995893, numHelperThreads -> 3 ]
Oct 16, 2012 11:55:07 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: schema export complete
Oct 16, 2012 11:55:07 AM org.hibernate.impl.SessionFactoryImpl checkNamedQueries
INFO: Checking 0 named queries
Hibernate: insert into CREDITCARDHOLDER (OWNER, CREDIT_CARD_HOLDER_ID) values (?, ?)
Hibernate: insert into CE_CREDIT_CARD_DETAILS (CREDIT_CARD_NUMBER, EXP_MONTH, EXP_YEAR, CREDIT_CARD_ID) values (?, ?, ?, ?)
Hibernate: insert into CREDITCARDHOLDER (OWNER, CREDIT_CARD_HOLDER_ID) values (?, ?)
Hibernate: insert into CE_BANK_ACCOUNT_DETAILS (ACCOUNT_TYPE, BANK_NAME, BANK_ACCOUNT_ID) values (?, ?, ?)
Oct 16, 2012 11:55:07 AM org.hibernate.impl.SessionFactoryImpl close
INFO: closing

If you run the following query against the superclass, 

Query query = session.createQuery("from CreditCardHolder");
List list = query.list();

Hibernate generates the following SQL statements,

Oct 16, 2012 12:22:36 PM org.hibernate.impl.SessionFactoryImpl checkNamedQueries
INFO: Checking 0 named queries
Hibernate: select creditcard0_.CREDIT_CARD_HOLDER_ID as CREDIT1_, 
creditcard0_.OWNER as OWNER0_, 
creditcard0_1_.CREDIT_CARD_NUMBER as CREDIT2_1_, 
creditcard0_1_.EXP_MONTH as EXP3_1_, 
creditcard0_1_.EXP_YEAR as EXP4_1_, 
creditcard0_2_.ACCOUNT_TYPE as ACCOUNT2_2_, 
creditcard0_2_.BANK_NAME as BANK3_2_, 
case 
when creditcard0_1_.CREDIT_CARD_ID is not null then 1 
when creditcard0_2_.BANK_ACCOUNT_ID is not null then 2 
when creditcard0_.CREDIT_CARD_HOLDER_ID is not null then 0 
end as clazz_ 
from CREDITCARDHOLDER creditcard0_ 
left outer join CE_CREDIT_CARD_DETAILS creditcard0_1_ 
on creditcard0_.CREDIT_CARD_HOLDER_ID=creditcard0_1_.CREDIT_CARD_ID 
left outer join CE_BANK_ACCOUNT_DETAILS creditcard0_2_ 
on creditcard0_.CREDIT_CARD_HOLDER_ID=creditcard0_2_.BANK_ACCOUNT_ID

Oct 16, 2012 12:22:36 PM org.hibernate.impl.SessionFactoryImpl close
INFO: closing

SQL case statment checks the existence of rows in the subclass tables and hibernate executes this query with Left Outer join.




1 comment: