java left logo
java middle logo
java right logo
 

Home arrow Java EE Tips
 
 
Main Menu
Home
Java Tutorials
Book Reviews
Java SE Tips
Java ME Tips
Java EE Tips
Other API Tips
Java Applications
Java Libraries
Java Games
Java Network
Java Forums
Java Blog




Most Visited Tips
Java SE Tips
Java ME Tips
Java EE Tips
Other API Tips
Java Applications
Java Libraries
Java Games
Book Reviews
Top Rated Tips
Java SE Tips
Java ME Tips
Java EE Tips
Other API Tips
Java Applications
Java Libraries
Java Games
Book Reviews


Statistics
Registered Users: 4098
Java SE Tips: 614
Java ME Tips: 202
Java EE Tips: 183
Other API Tips: 779
Java Applications: 298
Java Libraries: 209
Java Games: 16
Book Reviews:
 
 
 
Inheritance and the Java Persistence API E-mail
User Rating: / 40
PoorBest 

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:

   public abstract class Account{

    String name;
    String actNum; 
    String created;
    Status status; //OPEN/CLOSE
    //let's assume we are not that rich yet and stick to float 
    float  balance;      
    String description;

   }

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:

   @Entity
   @Inheritance(strategy=InheritanceStrategy.SINGLE_TABLE)
   public abstract class Account{

    public enum Status OPEN, CLOSED }

    @Id
    private  String acctNum;  
    private  String name;
    private  String created;
    private  Status status; //Open/Close
    //let's assume we are not that rich yet and stick to float 
    private  float balance;  
    private  String description;

   }

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.

   public abstract class BankAccount extends Account{

    String bankName;
 
   }

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:

   @MappedSuperclass
   public abstract class BankAccount extends Account{

    String bankName;
 
   }

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:

   @Entity
   public class SavingsAccount extends BankAccount{
   
   float savingsRate;
   ...

   }

   @Entity
   public class CheckingAccount extends BankAccount{

   float maintFee;
   ...

   }

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.

Polymorphic Queries

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:

   Account = em.find(Account.class, acctNum)

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:

   @Entity
   @AttributeOverride(
     name="bankName" column=@Column("name="bank_name"))
   public class CheckingAccount extends BankAccount{

     boolean isOverDraftAllowed;

   }

After the override, bankName maps to ACCOUNT.BANK_NAME instead of ACCOUNT.BANKNAME.

Inheritance Strategy

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:

   @Entity
   @Inheritance(strategy=InheritanceStrategy.SINGLE_TABLE)
   @DiscriminatorColumn(name="DCOL"
     discriminatorType=DiscriminatorType.STRING)
   @DiscriminatorValue("BaseAccount");
   public abstract class Account{
   ...

   }

   @Entity
   @DiscriminatorValue("Checking_Account");
   public class CheckingAccount extends BankAccount{
   ...
   
   float maintFee;

   }

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":

   @Entity
   @Inheritance(strategy=InheritanceStrategy.JOINED)
   @DiscriminatorColumn(name="DCOL"
     discriminatorType=DiscriminatorType.STRING)
   @DiscriminatorValue("BaseAccount");
   public abstract class Account{
   ....
   
   }

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:

   @Entity
   @PrimaryKeyJoinColumn(name="cca_id")
   public class CreditCardAccount extends Account{
   
    String issuingBank;
    String creditCardNumber;
    String expiresOn;
   
   }

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.

   @Entity
   @Inheritance(strategy=InheritanceStrategy.JOINED)
   public class BrokerageAccount extends Account{
   
    float tradeFees;
   
   }
   
   @Entity
   public class MarginAccount extends BrokerageAccount{
   
    float maxLoanAllowed;
   
   }

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.

Polymorphic Association

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.

   @Entity
   public class AccountOwner {
    String name;
    Collection <Account> accounts;
   
   }

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.

   public class BaseAccount {

    public float computeBalance(float principal, float rate){
     return principal * (1.0 + rate/100.0);
    }

   }

   @Entity
   public class Account extends BaseAccount{
   ....
   }

Summary

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:

  1. If you haven't already done so, download GlassFish from the GlassFish Community Downloads Page, and install it.
  2. 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.

  3. Download the sample package and extract its contents. You should now see the newly extracted directory as <sample_install_dir>/ttjun2006inheritance, where <sample_install_dir> is the directory in which you installed the sample package. The inherit directory below ttjun2006inheritance contains the source files and other support files for the sample.
  4. Change to the inherit directory and edit the build.properties file as appropriate. For example, if the admin host is remote, change the value of admin.host from the default (localhost) to the appropriate remote host. Also, make sure that the javaee.server.passwordfile location is correct.
  5. Start GlassFish:
  6.       $GLASSFISH_HOME/bin/asadmin start-domain domain1   
    
  7. Start the database server. From the inherit directory enter the following command:
  8.  
          ant dbsetup
    

    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.
    
  9. Build and deploy the sample application. From the inherit directory enter the following command:
  10.       ant all
    

    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.      
    
  11. Start the application. Open your browser to http://<host>:8080/sample/index.html, replacing <host> with your host name (for instance, localhost). You should see the home page of the application.
  12. 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.


 Related Tips

 
< Prev   Next >

Page 1 of 0 ( 0 comments )

You can share your information about this topic using the form below!

Please do not post your questions with this form! Thanks.


Name (required)


E-Mail (required)

Your email will not be displayed on the site - only to our administrator
Homepage(optional)



Comment Enable HTML code : Yes No



 
       
         
     
 
 
 
   
 
 
java bottom left
java bottom middle
java bottom right
RSS 0.91 FeedRSS 1.0 FeedRSS 2.0 FeedATOM FeedOPML Feed

Home - About Us - Privacy Policy
Copyright 2005 - 2008 www.java-tips.org
Java is a trademark of Sun Microsystems, Inc.