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

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.

3.1. Make the Java data model

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:

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

  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 18.5.  The Java data model of the Person class hierarchy

3.2. New issues

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:

  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. Encode the model classes of the Java data model

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.

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:

@Inheritance( strategy = InheritanceType.JOINED)
@DiscriminatorColumn( name = "category", discriminatorType = DiscriminatorType.STRING, length = 16)
@Table( name = "persons")
public abstract class Person {
  @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:

@DiscriminatorValue( value = "AUTHOR")
@Table( name = "authors")
@ManagedBean( name = "author")
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:

@DiscriminatorValue( value = "EMPLOYEE")
@Table( name = "employees")
@ManagedBean( name = "employee")
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.

3.3.2. Database schema for joined table class hierarchy

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:

  `PERSONID` int(11) NOT NULL,
  `category` varchar(16) DEFAULT NULL,
  `NAME` varchar(255) NOT NULL

  `PERSONID` int(11) NOT NULL,

  `PERSONID` int(11) NOT NULL,
  `EMPNO` int(11) NOT NULL,
  `TYPE` varchar(32) DEFAULT NULL

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.

3.4. Write the View and Controller Code

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:

  <f:facet name="header">Special type of employee</f:facet>
  #{e.type != null ? e.type.label.concat( " of ").concat( e.department).concat( " department")  : ""}

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.