The starting point for our case study is the design model shown in Figure 18.2 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 data model that we derive
from the design model by essentially leaving the
generalizatiion 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 18.5 below. Notice
that we have also made two technical design decisions:
We have declared the segmentation of Person into
Employee and Author to be complete,
that is, any person is an employee or an author (or both).
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 18.4 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 data model shown in Figure 18.5 above is encoded by using the JavaBeans
Person, Employee and Author as well as
for the enmueration type EmployeeTypeEL.
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
@Inheritance( strategy = InheritanceType.JOINED)
@DiscriminatorColumn( name = "category", discriminatorType = DiscriminatorType.STRING, length = 16)
@Table( name = "persons")
public abstract class Person {
@Id
@PositiveInteger
@NotNull( message = "A person ID value is required!")
private Integer personId;
@Column( nullable = false)
@NotNull( message = "A name is required!")
private String name;
// constructors, set, get and other methods
}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
@DiscriminatorValue( value = "AUTHOR")
@Table( name = "authors")
@ManagedBean( name = "author")
@ViewScoped
public class Author extends Person {
@NotNull( message = "A biography is required!")
private String biography;
// constructors, set, get and other methods
}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.
Last we define the Employee
class:
@Entity
@DiscriminatorValue( value = "EMPLOYEE")
@Table( name = "employees")
@ManagedBean( name = "employee")
@ViewScoped
public class Employee extends Person {
@Column( nullable = false)
@NotNull( message = "An employee ID is required!")
@Min( value = 20000, message = "Employee no. must be greater than 20000!")
@Max( value = 99999, message = "Employee no. must be lower than 100000!")
private Integer empNo;
@Column( nullable = false, length = 32)
@Enumerated( EnumType.STRING)
private EmployeeTypeEL type;
@Column( nullable = true, length = 64)
private String department;
// constructors, set, get and other methods
}Notice the type property used to identify the
Employee type, such as Manager. Its values are
defined by the EmployeeTypeEL enumeration.
As a result of the @Inheritance( strategy =
InheritanceType.JOINED) annotation, 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 JavaBean 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.
The user interface (UI) is very similar with the one for the Book hierarchy shown earlier
in this tutorial. For every JavaBean class, we have a controller class which
contains the add, update and destroy 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 JavaBean in the inheritance hierarchy we define a set of
views corresponding to CRUD operations. For example, in the case of
Author we have WebContent/views/authors/{listAll, create,
update, destroy}.xhtml files. In the case of Employee, the
List All Employees test case require 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>It is interesting to notice that within JSF expressions we can' use the + (plus)
operator to concatenate Java strings. JSF EL expression allows the +
operator to be used only with number types. However, we can use the
concat method available to any String object.
The Create, Update and Delete test cases for
both cases, Author and Employee are similar whith what we
have learned in this tutorial as well as in the Part 1 to 5 tutorials.