3. Case Study 2: Implement a Class Hierarchy with Joined Table Inheritance

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.

3.1. Make the Java Entity class model

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:

  1. 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.

  2. 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.

Figure 14.2. The Java Entity class model of the Person class hierarchy

3.2. New issues

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:

  1. 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.

  2. 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.

3.3. Code the model classes of the Java Entity class model

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.

3.3.1. Define the category relationships

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.

3.3.2. Database schema for joined table class hierarchy

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.

3.4. Write the View and Controller Code

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.