Pages

Table per class hierarchy

Table per class hierarchy

In this strategy, properties of subclasses is mapped to columns of one database table i.e whole class  hierarchy can be mapped to a single table. concrete sub-class (which was represented a row in a table) now can be represented by the value of discriminator column.

This strategy performs well for polymorphic and non-polymorphic queries.

Note: This strategy has one major problem, columns for properties declared by subclasses must be declared as nullable and normalization is second important issue.

Let's follow the same example in my previous post, we have done some modification according to this strategy. Now, we have one table (CREDIT_CARD_HOLDER) for CreditCardHolder class and this class is not abstract.

See the modification in CreditCardHolder.hbm.xml file,


<hibernate-mapping package="table.per.concrete_class">
<class name="CreditCardHolder" dynamic-insert="true" dynamic-update="true">
<id name="creditCardHolderId" column="CREDIT_CARD_HOLDER_ID" type="long">
<generator class="increment"/>
</id>

<!-- This element must be the first element after the id element in this mapping file -->
<discriminator column="DIS_CREDIT_CARD_HOLDER_VALUE"/>

<property name="owner" column="OWNER"/>
 
<subclass name="CreditCardDetails" discriminator-value="CC">
<property name="number" column="CREDIT_CARD_NUMBER"/>
<property name="expMonth" column="EXP_MONTH"/>
<property name="expYear" column="EXP_YEAR"/>
</subclass>

<subclass name="BankAccountDetails" discriminator-value="BA">
<property name="account" column="ACCOUNT_TYPE"/>
<property name="bankName" column="BANK_NAME"/>
</subclass>
</class>
</hibernate-mapping>

we have added a discriminator element in this xml mapping metadata, it is not a property in any class in the hierarchy. This column is used to distinguish between persistent classes, it is internally used by hibernate.

If you run the HibernateTest.java class which we have mentioned in my previous post. That time, it generates the following hibernate SQL statement at console.


09:51:35,006 INFO HibernateTest:22 - starting of main method
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Environment <clinit>
INFO: Hibernate 3.0rc1
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Environment <clinit>
INFO: hibernate.properties not found
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Environment <clinit>
INFO: using CGLIB reflection optimizer
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Environment <clinit>
INFO: using JDK 1.4 java.sql.Timestamp handling
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration configure
INFO: configuring from resource: /hibernate.cfg.xml
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration getConfigurationInputStream
INFO: Configuration resource: /hibernate.cfg.xml
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration addResource
INFO: Mapping resource: table/per/concrete_class/CreditCardHolder.hbm.xml
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.HbmBinder bindRootPersistentClassCommonValues
INFO: Mapping class: table.per.concrete_class.CreditCardHolder -> CREDITCARDHOLDER
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.HbmBinder bindSubclass
INFO: Mapping subclass: table.per.concrete_class.CreditCardDetails -> CREDITCARDHOLDER
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.HbmBinder bindSubclass
INFO: Mapping subclass: table.per.concrete_class.BankAccountDetails -> CREDITCARDHOLDER
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration doConfigure
INFO: Configured SessionFactory: null
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing extends queue
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing collection mappings
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing association property references
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing foreign key constraints
Oct 16, 2012 9:51:35 AM org.hibernate.dialect.Dialect <init>
INFO: Using dialect: org.hibernate.dialect.MySQLDialect
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Maximum outer join fetch depth: 2
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default batch fetch size: 1
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Generate SQL with comments: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL updates by primary key: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory createQueryTranslatorFactory
INFO: Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
Oct 16, 2012 9:51:35 AM org.hibernate.hql.ast.ASTQueryTranslatorFactory <init>
INFO: Using ASTQueryTranslatorFactory
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query language substitutions: {}
Oct 16, 2012 9:51:35 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: C3P0 using driver: null at URL: jdbc:mysql://localhost:3306/HibernateTest
Oct 16, 2012 9:51:35 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: Connection properties: {autocommit=true, user=root, password=****}
Oct 16, 2012 9:51:35 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: autocommit mode: true
Oct 16, 2012 9:51:35 AM org.hibernate.connection.C3P0ConnectionProvider configure
WARNING: No JDBC Driver class was specified by property hibernate.connection.driver_class
09:51:35,286 INFO MLog:80 - MLog clients using log4j logging.
09:51:35,521 INFO C3P0Registry:204 - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
09:51:35,577 INFO AbstractPoolBackedDataSource:462 - Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@19d8fa13 [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@8cc667b0 [ 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 -> 1bs1yqt8qssvro27bn6e7|186e8cb, 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@d167c8b4 [ description -> null, driverClass -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qssvro27bn6e7|aa91ef, jdbcUrl -> jdbc:mysql://localhost:3306/HibernateTest, properties -> {autocommit=true, user=******, password=******} ], preferredTestQuery -> null, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false; userOverrides: {} ], dataSourceName -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qssvro27bn6e7|8487ba, numHelperThreads -> 3 ]
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch size: 15
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch updates for versioned data: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Scrollable result sets: enabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC3 getGeneratedKeys(): enabled
Oct 16, 2012 9:51:35 AM org.hibernate.transaction.TransactionFactoryFactory buildTransactionFactory
INFO: Using default transaction strategy (direct JDBC transactions)
Oct 16, 2012 9:51:35 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 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic flush during beforeCompletion(): disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic session close at end of transaction: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory createCacheProvider
INFO: Cache provider: org.hibernate.cache.EhCacheProvider
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Second-level cache: enabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Optimize cache for minimal puts: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Structured second-level cache entries: enabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query cache: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Echoing all SQL to stdout
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Statistics: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Deleted entity synthetic identifier rollback: disabled
Oct 16, 2012 9:51:35 AM org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default entity-mode: pojo
Oct 16, 2012 9:51:35 AM org.hibernate.impl.SessionFactoryImpl <init>
INFO: building session factory
Oct 16, 2012 9:51:35 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 9:51:36 AM org.hibernate.impl.SessionFactoryObjectFactory addInstance
INFO: Not binding factory to JNDI, no JNDI name configured
Oct 16, 2012 9:51:36 AM org.hibernate.dialect.Dialect <init>
INFO: Using dialect: org.hibernate.dialect.MySQLDialect
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing extends queue
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing collection mappings
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing association property references
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing foreign key constraints
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing extends queue
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing collection mappings
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing association property references
Oct 16, 2012 9:51:36 AM org.hibernate.cfg.Configuration secondPassCompile
INFO: processing foreign key constraints
Oct 16, 2012 9:51:36 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: Running hbm2ddl schema export
Oct 16, 2012 9:51:36 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: exporting generated schema to database
Oct 16, 2012 9:51:36 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: C3P0 using driver: null at URL: jdbc:mysql://localhost:3306/HibernateTest
Oct 16, 2012 9:51:36 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: Connection properties: {autocommit=true, user=root, password=****}
Oct 16, 2012 9:51:36 AM org.hibernate.connection.C3P0ConnectionProvider configure
INFO: autocommit mode: true
Oct 16, 2012 9:51:36 AM org.hibernate.connection.C3P0ConnectionProvider configure
WARNING: No JDBC Driver class was specified by property hibernate.connection.driver_class
09:51:36,197 INFO AbstractPoolBackedDataSource:462 - Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@1b5d676a [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@23fb0718 [ 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 -> 1bs1yqt8qssvro27bn6e7|264dde, 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@2442a798 [ description -> null, driverClass -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qssvro27bn6e7|21351e, jdbcUrl -> jdbc:mysql://localhost:3306/HibernateTest, properties -> {autocommit=true, user=******, password=******} ], preferredTestQuery -> null, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false; userOverrides: {} ], dataSourceName -> null, factoryClassLocation -> null, identityToken -> 1bs1yqt8qssvro27bn6e7|76cfab, numHelperThreads -> 3 ]
Oct 16, 2012 9:51:36 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: schema export complete
Oct 16, 2012 9:51:36 AM org.hibernate.impl.SessionFactoryImpl checkNamedQueries
INFO: Checking 0 named queries
Hibernate: insert into CREDITCARDHOLDER (OWNER, CREDIT_CARD_NUMBER, EXP_MONTH, EXP_YEAR, DIS_CREDIT_CARD_HOLDER_VALUE, CREDIT_CARD_HOLDER_ID) values (?, ?, ?, ?, 'CC', ?)
Hibernate: insert into CREDITCARDHOLDER (OWNER, ACCOUNT_TYPE, BANK_NAME, DIS_CREDIT_CARD_HOLDER_VALUE, CREDIT_CARD_HOLDER_ID) values (?, ?, ?, 'BA', ?)
Oct 16, 2012 9:51:36 AM org.hibernate.impl.SessionFactoryImpl close
INFO: closing


Now, If you run the query against CreditCardHolder to retrieve the all instances of subclasses in the hierarchy.


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

please look the hibernate generated statments on console.

select creditcard0_.CREDIT_CARD_HOLDER_ID as CREDIT1_, 
creditcard0_.OWNER as OWNER0_, 
creditcard0_.CREDIT_CARD_NUMBER as CREDIT4_0_, 
creditcard0_.EXP_MONTH as EXP5_0_, 
creditcard0_.EXP_YEAR as EXP6_0_, 
creditcard0_.ACCOUNT_TYPE as ACCOUNT7_0_, 
creditcard0_.BANK_NAME as BANK8_0_, 
creditcard0_.DIS_CREDIT_CARD_HOLDER_VALUE as DIS2_ 
from CREDITCARDHOLDER creditcard0_

Note : If we do not specify the discriminator column value in the xml mapping metadata file, that time it automatically generates 'class' column (type String) in the table. see the hibernate generated SQL statement again.


INFO: Checking 0 named queries
Hibernate: insert into CREDITCARDHOLDER (OWNER, CREDIT_CARD_NUMBER, EXP_MONTH, EXP_YEAR, class, CREDIT_CARD_HOLDER_ID) values (?, ?, ?, ?, 'CC', ?)
Hibernate: insert into CREDITCARDHOLDER (OWNER, ACCOUNT_TYPE, BANK_NAME, class, CREDIT_CARD_HOLDER_ID) values (?, ?, ?, 'BA', ?)
Oct 16, 2012 11:12:50 AM org.hibernate.impl.SessionFactoryImpl close


The disadvantage of this strategy may be a major problem for designing , denormalization schema can be a major problem of this strategy.

4 comments: