This Tech Tip reprinted with permission by java.sun.com
Inheritance, the ability of a subclass to derive state and behavior from its superclass, was not supported previously in Enterprise JavaBeans (EJB) technology. In the EJB 2.1 persistence model, an entity could not derive from another entity. However, the new Java Persistence API, a part of the EJB 3.0 specification (JSR 220) , changes that. By supporting inheritance, the Java Persistence API allows you to write more modular, cohesive, and extensible code. It also allows you to take advantage of polymorphism, the ability of an object to take different forms.
This Tech Tip presents some of the features of inheritance supported in the Java Persistence API. A sample package accompanies the Tech Tip. It demonstrates some of the features discussed in the tip. The examples in the tip are taken from the source code for the sample (which is included in the package). The sample uses an open source reference implementation of Java EE 5 called GlassFish. You can download GlassFish from the GlassFish Community Downloads page.
A Simple Example of Inheritance
Let's say you made a resolution this year to be more organized. In particular, you resolved to better organize your financial accounts. Assume you have the following accounts: checking, savings, credit card, brokerage, and margin. All of these are accounts, so you can model them based on a class called Account, which is a Plain Old Java Object (POJO). Here's a snippet of code from the Account class:
Inheriting from Abstract Entities
Notice that Account is an abstract class, something that can't be directly instantiated as an object. However, in spite of that, the Java Persistence API allows for Account to be an entity. Significantly, this enables you to use Account in polymorphic queries written in the Java Persistence Query Language. Through these queries you can load instances of Account subclasses using only the account number. Also, there are many attributes in Account that can be inherited by more specific entities (the tip will show these entities soon), so making Account an entity allows you to inherit the mapping attributes. To make Account an entity you mark the class with the @Entity annotation. To identify the mapping strategy for the class and its subclasses you mark the class with an @Inheritance annotation. Here's the Account class snippet with @Entity and @Inheritance annotations added:
Notice also, the @Id annotation, which marks the acctNum field as the primary key in the Account entity.
The strategy element value SINGLE_TABLE in the @Inheritance annotation means that the base class Account and any of its subclasses are mapped to a single table.
There is a way to inherit mapping attributes from Account without making it an entity. In that approach you annotate Account with the @MappedSuperclass annotation. But that approach does not allow you to use Account in Java Persistence Query Language queries. Also, depending on the inheritance strategy you choose, it might also result in base class attributes being mapped to separate tables defined for the more specific classes.
Inheriting from Abstract Non-Entities (Mapped Superclass)
Both the checking and savings accounts are bank accounts, so for these two kinds of accounts you can define yet another abstract class, BankAccount. The BankAccount class is abstract because it doesn't need to be instantiated -- its subclasses that model the checking and savings account do. Also, BankAccount inherits its mapping attributes along with the behavior from the Account class.
There's no @Entity annotation in BankAccount -- it's defined as an abstract non-entity class. Because more specific classes will be derived from it, you can annotate BankAccount as a mapped superclass:
You might ask why use that approach if it doesn't allow you to use BankAccount in Java Persistence Query Language queries? In fact, because it's not an entity, BankAccount is not mapped to a database table. However these functions are not needed in this example. The mapped superclass approach does enable you to inherit both behavior and mapping attributes from BankAccount. In a case where you need to use the BankAccount and bank name in a polymorphic query, you would define the class as an abstract entity.
There are two kinds of concrete bank account entities -- SavingsAccount and CheckingAccount. Here are snippets of those classes:
The attribute bankName maps to ACCOUNT.BANKNAME for InheritanceType.SINGLE_TABLE and SavingsAccount.BANKNAME for InheritanceType.JOINED.
Similarly, for CheckingAccount, bankName maps to ACCOUNT.BANKNAME for InheritanceType.SINGLE_TABLE and CheckingAccount.BANKNAME for InheritanceType.JOINED.
Because the Java Persistence API supports polymorphic queries, you can find any concrete instance of the Account entity by its account number (acctNum). You can do this using the entity manager's find by primary key method or you can write your own Java Persistence query language query. Here's a statement that runs such a query:
In the statement, em is an EntityManager instance that is used to find entities by their primary key. An EntityManager is a Java Persistence API class that manages the life cycle of entities within a persistence context, as well as enabling queries to be run on entities.
The query looks up a specific account object instance using the Account class and acctNum, which contains the account number. If the value of acctNum matches an account number of a savings or checking account, the savings or checking account is loaded by the persistence provider. That's true even though you pass an Account class (not a SavingsAccount or CheckingAccount class) in the query. This is a demonstration of polymorphism. When Account is queried, all subclasses that meet the query criteria are returned. If Account was not an entity, but a mapped superclass, you wouldn't be able to run the query as shown above.
Overriding Mapping in Inherited Classes
In an inherited class you can override the mappings that you specified in a mapped superclass. For example, you can override the mapping attribute bankName in the CheckingAccount class as follows:
After the override, bankName maps to ACCOUNT.BANK_NAME instead of ACCOUNT.BANKNAME.
The Java Persistence API allows for three different inheritance strategies that dictate how subclasses are mapped to database tables. The three strategies are single table per class hierarchy, joined subclass, and single table per class.
Single Table Per Class Hierarchy Strategy
This strategy, which is specified by the strategy value "SINGLE_TABLE", means that a class and all of its subclasses are mapped to the same table. If you recall, this is the strategy shown previously in the ACCOUNT class. In a single table per class hierarchy the instances are distinguished by a discriminator value in a discriminator column.
The default name for the discriminator column name is DTYPE, and the default type of the column is STRING. If you don't specify a discriminator value, it defaults to a provider-generated value. For a discriminator column of type STRING, the default discriminator name is the entity name. For example, if you accept all the defaults for Account, the discriminator column value is "ACCOUNT". You can however override the defaults for the discriminator column and value, using the @DiscriminatorColumn and @DiscriminatorValue annotations, respectively. For example:
After these specifications, the discriminator column has the name "DCOL", and records that represent a checking account have a DCOL column value of "Checking_Account".
Here are the column names and their types for the table that would be generated based on specifications for ACCOUNT and its subclasses:
Name Null? Type ---------------------------------------------- ACCTNUM NOT NULL VARCHAR2(255) DCOL VARCHAR2(31) CREATED VARCHAR2(255) STATUS NUMBER(10) NAME VARCHAR2(255) BALANCE NUMBER(19,4) DESCRIPTION VARCHAR2(255) TRADEFEES NUMBER(19,4) MAXLOANALLOWED NUMBER(19,4) CREDITCARDNUMBER VARCHAR2(255) ISSUINGBANK VARCHAR2(255) EXPIRESON VARCHAR2(255) BANKNAME VARCHAR2(255) SAVINGSRATE NUMBER(19,4) BANK_NAME VARCHAR2(255) ISOVERDRAFTALLOWED NUMBER(1) Table: ACCOUNT
Notice that the table contains all the attributes of the entire class hierarchy. So for an account of type CheckingAccount, there is only one record representing CheckingAccount, BankAccount, and Account instances. This record has a DTYPE value of "Checking_Account" (that's what was specified in the @DiscriminatorValue annotation). If your persistence provider has the ability to create the database tables based on the meta information, you do not have to be concerned about the database schema.
Join Subclass Strategy
In the joined subclass strategy, which is specified by the strategy value "JOINED", the attributes of the base class are stored in a single table, and the attributes of the subclasses are stored in separate tables. So in the code for ACCOUNT, if you changed the inheritance strategy to "JOINED":
the database tables would like the following:
Name Null? Type ---------------------------------------------- ACCTNUM NOT NULL VARCHAR2(255) DCOL VARCHAR2(31) CREATED VARCHAR2(255) STATUS NUMBER(10) NAME VARCHAR2(255) BALANCE NUMBER(19,4) DESCRIPTION VARCHAR2(255) Table: Account Name Null? Type ----------------------------------------------- ACCTNUM NOT NULL VARCHAR2(255) BANKNAME VARCHAR2(255) SAVINGSRATE NUMBER(19,4) Table: SAVINGSACCOUNT Name Null? Type ---------------------------------------------- ACCTNUM NOT NULL VARCHAR2(255) BANK_NAME VARCHAR2(255) ISOVERDRAFTALLOWED NUMBER(1) Table: CHECKINGACCOUNT Name Null? Type ---------------------------------------------- ACCTNUM NOT NULL VARCHAR2(255) TRADEFEES NUMBER(19,4) Table: BROKERAGEACCOUNT Name Null? Type ---------------------------------------------- ACCTNUM NOT NULL VARCHAR2(255) MAXLOANALLOWED NUMBER(19,4) Table: MARGINACCOUNT
Even though no primary key was specified in the subclasses of Account, each of the tables representing the subclasses has a primary key. The primary key has the same name and type as the primary key defined for the Account entity -- acctNum. The subclasses and the base class are joined on the primary keys when a query is executed to load a subclass entity. Notice how, in the joined subclass strategy, each of the tables representing a subclass has only the attributes that are not contained in the base class.
Single Table Per Class
In single table per class, each class is mapped to a separate table. A persistence provider is not required to support this strategy with this release of the Java Persistence API specification.
Overriding a Join Column
In the joined subclass strategy you saw that the join between the base class and the subclasses happen on the primary keys. The primary key of the subclass has the same type and name as the base class. It is possible to specify a primary key join column in a subclass and override the default. This is demonstrated in the credit card account entity:
When the persistence provider does the join between the tables representing the CreditCardAccount and Account entities, to load a CreditCardAccount instance, the join will now happen between cca_id (the primary key column) of CreditCardAccount and the acctNum column of the Account column. If you did not override default primary key join column, the join would happen on the acctNum columns of the tables representing the entities.
Overriding Inheritance Strategy
It's possible for an entity to specify a different inheritance strategy for its subclasses. The BrokerageAccount entity illustrates that.
Because the Account entity specifies a SINGLE_TABLE per class hierarchy strategy, Account, CheckingAccount, SavingsAccount, CreditCardAccount and BrokerageAccount are stored in the same database table. However the MarginAccount entity's maxLoanAllowed attribute is stored in a separate table because the inheritance strategy was changed to "JOINED" in the BrokerageAccount. A persistence provider does not need to support this feature, but the Java API specification allows for this. So to make sure that your code is portable across vendors, it's best to avoid using this feature.
Notice that MarginAccount is derived from another concrete entity -- BrokerageAccount.
The Java Persistence API allows polymorphic associations. Every account has an owner and an account owner typically has multiple accounts. These accounts do not have to be of the same kind. Also, you don't want to have a member variable in AccountOwner represent each account kind type -- that isn't extensible or efficient. Fortunately you can use a collection of Account entities to represent all the different types of accounts an AccountOwner entity has (just as in POJOs). Note that although you can have a collection of Account objects, you cannot directly instantiate Account. An account owner can have a collection of different kinds of accounts. The ability to have polymorphic associations allows you to have more specific account class instances in the collection of Account.
A mapped superclass cannot be the target of a persistent relationship. So if you made Account a mapped superclass, you wouldn't be able to have a one-to-many relationship between an AccountOwner and Account.
Inheriting From a Non-Entity Class
Typically you inherit an entity from a non-entity to inherit its behavior. The attributes you inherit from a non-entity are not persisted. For example, the BaseAccount class provides the basic functionality of computing the balance, given a principal amount and interest rate. You can extend Account from BaseAccount to inherit this behavior.
The new Java Persistence API enables you to write more modular, cohesive, and extensible code through inheritance, polymorphic queries, and polymorphic associations. It benefits both developers who are creating an application data model from scratch and developers who want to migrate their current persistence implementation to move to a standardized persistence API.
Running the Sample Code
To install and run the sample code that accompanies this tip:
- If you haven't already done so, download GlassFish from the GlassFish Community Downloads Page, and install it.
- Set the following environment variables:
- GLASSFISH_HOME. This should point to where you installed GlassFish.
- ANT_HOME. This should point to where ant is installed. Ant is included in the GlassFish bundle that you downloaded. (In Windows, it's in the lib\ant subdirectory.)
- JAVA_HOME. This should point to the location of JDK 5.0 on your system.
Add $JAVA_HOME/bin, $ANT_HOME/bin, and $GLASSFISH_HOME/bin to your PATH environment variable.
$GLASSFISH_HOME/bin/asadmin start-domain domain1
In response you should see output similar to this:
... start-db: [exec] Database started in Network Server mode on host ... and port ... [exec] Starting database in the background. Log redirected to ... [exec] Command start-database executed successfully.
In response you should see output similar to this:
compile: [javac] Compiling 11 source files to ... ...\inherit\build\classes package-ejb: ... [jar] Building jar: ...\inherit\build\ejb\sample.jar package-war: ... [zip] Building zip: ...\inherit\build\sample.ear tools: deploy: [exec] Command deploy executed successfully.
Try creating a new account and searching for an account. Underlying the functions of the application is the support for inheritance in the Java Persistence API.
About the Author
Rahul Biswas is a member of the Java Performance Engineering group at Sun. He is currently involved in the development of a generic performance benchmark for Java Persistence and the performance improvement of the persistence implementation in GlassFish.
Copyright (c) 2004-2005 Sun Microsystems, Inc.
All Rights Reserved.