The starting point for our case study is the design model shown in Figure 12.8 above. In the following sections, we show how to eliminate the Manager
class by using the Class Hierarchy Merge design pattern and how to implement the Person
hierarchy and use Joined, Multiple Table Inheritance storage with the help of JPA framework.
We design the model classes of our example app with the help of a Java Entity class model that we derive from the design model by essentially leaving the generalization arrows as they are and just adding getters and setters to each class. However, in the case of our example app, it is natural to apply the Class Hierarchy Merge design pattern to the segmentation of Employee
for simplifying the data model by eliminating the Manager
subclass. This leads to the model shown in Figure 14.2 below. Notice that we have also made two technical design decisions:
We have declared the segmentation of Person
into Employee
and Author
to be disjoint and complete, that is, any person is either an employee or an author. Since Java does not support multiple classification, we cannot implement an overlapping segmentation, as needed when modeling roles, with Java.
We have turned Person
into an abstract class (indicated by its name written in italics in the class rectangle), which means that it cannot have direct instances, but only indirect ones via its subclasses Employee
and Author
. This technical design decision is compatible with the fact that any Person
is an Employee
or an Author
(or both), and consequently there is no need for any object to instantiate Person
directly.
Compared to the model of our first case study, shown in Figure 14.1 above, we have to define the category relationships between Employee
and Person
, as well as between Author
and Person
, using the JPA annotation.
In the UI code we have to take care of:
Adding the views (in the folders WebContent/views/authors
and WebContent/views/employees
) and controller classes (AuthorController
and EmployeeController
) for the corresponding Author
and Employee
model classes.
Deal with the Manager
case, by adding a "Special type" select control, in the forms of the "Create book" and "Update book" use cases in WebContent/views/books/create.xhtml
and WebContent/views/books/update.xhtml
. Segment property form fields (i.e., department in our example) are only displayed, and their validation is performed, when a corresponding employee type has been selected.
The Java Entity class model shown in Figure 14.2 above is coded by using the JavaBeans Person
, Employee
and Author
as well as for the enumeration type EmployeeCategoryEL
.
We define the category relationships between Employee
and Person
, as well as between Author
and Person
, using the JPA annotations. At first we create the Person
class as shown below:
@Entity @Table(name="persons") @Inheritance(strategy=InheritanceType.JOINED) @DiscriminatorColumn(name="category", discriminatorType=DiscriminatorType.STRING, length=16) public abstract class Person { @Id @NotNull(message="A person ID is required!") private Integer personId; @NotNull(message="A name is required!") @Column(nullable=false) private String name; ... }
Comparing with the Book
hierarchy shown in Test Case 1, the @Inheritance
annotations defines now the strategy=InheritanceType.JOINED
. This means, for every class in the inheritance hierarchy, a database table is used. The @DiscriminatorColumn(name="category")
specifies the column in the corresponding table (i.e., persons
) of the top hierarchy class (i.e., Person
) which stores the discriminator values used to identify the stored type of each entry (table row).
Notice that the Java class Person
is declared as being abstract
, which means it can't be initialized, instead we can and we initialize subclasses derived from it (i.e., Employee
and Author
). This also mean that we don't declare a @DiscriminatorValue
because no direct instance of Person
is stored in the database table.
Further, we define the Author
class as follows:
@Entity @Table(name="authors") @DiscriminatorValue( value="AUTHOR") @ManagedBean(name="author") @ViewScoped public class Author extends Person { @NotNull(message="A biography is required!") private String biography; ... }
The Author
class inherits Person
, therefore the get and set methods corresponding to personId
and name
properties are available. The @DiscriminatorValue(value="AUTHOR")
specifies that the column category of the persons
table stores the value AUTHOR
for every entry which comes from persisting an Author
instance.
Finally, we define the Employee
class:
@Entity @Table(name="employees") @DiscriminatorValue(value="EMPLOYEE") @ManagedBean(name="employee") @ViewScoped public class Employee extends Person { @Column( nullable=false) @NotNull( message="An employee ID is required!") private Integer empNo; @Column( nullable=false, length=32) @Enumerated( EnumType.STRING) private EmployeeCategoryEL type; @Column( nullable=true, length=64) private String department; ... }
Notice the type
property used to identify the Employee
type, such as Manager
. Its values are defined by the EmployeeCategoryEL
enumeration.
As a result of the annotation
@Inheritance( strategy = InheritanceType.JOINED)
for each class in the inheritance hierarchy, one database table is created. The corresponding simplified SQL-DDL scripts used by JPA to create the persons
, authors
and employees
tables are shown below:
CREATE TABLE IF NOT EXISTS `persons` ( `PERSONID` int(11) NOT NULL, `category` varchar(16) DEFAULT NULL, `NAME` varchar(255) NOT NULL ); CREATE TABLE IF NOT EXISTS `authors` ( `PERSONID` int(11) NOT NULL, `BIOGRAPHY` varchar(255) DEFAULT NULL ); ADD CONSTRAINT `FK_authors_PERSONID` FOREIGN KEY (`PERSONID`) REFERENCES `persons` (`PERSONID`); CREATE TABLE IF NOT EXISTS `employees` ( `PERSONID` int(11) NOT NULL, `DEPARTMENT` varchar(64) DEFAULT NULL, `EMPNO` int(11) NOT NULL, `TYPE` varchar(32) DEFAULT NULL ); ADD CONSTRAINT `FK_employees_PERSONID` FOREIGN KEY (`PERSONID`) REFERENCES `persons` (`PERSONID`);
As we can see, every table contains the direct properties as defined by the corresponding Java bean class. Additionally, the authors
and employees
tables are created with a foreign key constraing for the PERSONID
column refering to to the PERSONID
column from the persons
table.
In the user interface, for every Java bean class, we have a controller class which contains the create
, update
and delete
CRUD methods. The PersonController
class is defined as abstract and contains the checkPersonIdAsId
method, which is common to all subclasses. The AuthorController
and EmployeeController
inherits the PersonController
.
For every non-abstract entity class in the inheritance hierarchy we define a set of views corresponding to CRUD operations. For example, in the case of Author
we have the files retrieveAndListAll.xhtml
, create.xhtml
, update.xhtml
, and delete.xhtml
in the folder WebContent/views/authors/
. In the case of Employee
, the Retrieve/List All Employees use case requires to display the Special type of employee column:
<h:column> <f:facet name="header">Special type of employee</f:facet> #{e.type != null ? e.type.label.concat(" of ").concat( e.department).concat(" department") : ""} </h:column>
Notice that within EL expressions we cannot use the + (plus) operator for concatenating strings. EL allows the +
operator to be used only for numeric addition. However, we can use the concat
method instead.