|
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:
- 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.
- 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.
- 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.
- Start GlassFish:
$GLASSFISH_HOME/bin/asadmin start-domain domain1
- Start the database server. From the inherit directory enter
the following command:
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.
- Build and deploy the sample application. From the inherit
directory enter the following command:
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.
- 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.
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
|
You can share your information about this topic using the form below!
Please do not post your questions with this form! Thanks.