2. Case Study 1: Implement a Class Hierarchy with Single Table Inheritance

In this example we implement the Book hierarchy shown in Figure 12.1. The Java Entity class model is derived from this design model.

2.1. Make the Java Entity class model

Recall that we obtain a Java Entity class modelfrom an OO class model by (1) making all properties private, (2) using Java datatype classes, (3) adding public getters and setters, (4) adding a toString function, (5) adding the (static) data storage methods create, retrieve, update and delete, resulting in the model shown in Figure 14.1.

Figure 14.1. The Java Entity class model of the Book class hierarchy

2.2. New issues

We have to deal with a number of new issues:

  1. In the model code we have to take care of:

    1. Coding the enumeration type (BookCategoryEL) that is used in the UI code for creating a selection list widget that allows choosing the category of book.

    2. Code each class from the Book class hierarchy using suitable JPA annotations for persistent storage in a corresponding database table, like books.

  2. In the UI code we have to take care of:

    1. Adding a "Special type" column to the display table of the "retrieve/list all books" use case in retrieveAndListAll.xhtml in the folder WebContent/views/books/. A book without a special category will have an empty table cell, while for all other books their category will be shown in this cell, along with other segment-specific attribute values.

    2. Adding a "Special type" select control, and corresponding form fields for all segment properties, in the forms of the "Create book" and "Update book" use cases in create.xhtml and update.xhtml in the folder WebContent/views/books/. Segment property form fields are only displayed, and their validation is performed, when a corresponding book category has been selected. Such an approach of rendering specific form fields only on certain conditions is sometimes called dynamic forms.

2.3. Code the classes of the Java Entity class model

The Java Entity class model can be directly coded for getting the code of the model classes of our Java EE back-end app.

2.3.1. Summary

  1. Code the enumeration type (to be used in the facelets) .

  2. Code the model classes and add the corresponding JPA annotations for class hierarchy.

These steps are discussed in more detail in the following sections.

2.3.2. Code the enumeration type BookCategoryEL

The enumeration type BookCategoryEL is coded as a Java enum, providing both enumeration literals as well as (human-readable) labels:

public enum BookCategoryEL {
  TEXTBOOK("TextBook"), BIOGRAPHY("Biography");
  private final String label;

  private BookCategoryEL( String label) {
    this.label = label;
  }
  public String getLabel() {return this.label;}
}
2.3.2.1. Code the model classes

We code the model classes Book, TextBook and Biography in the form of Java Entity classes using suitable JPA annotations:

@Entity @Table( name="books")
@Inheritance( strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn( name="category", 
    discriminatorType=DiscriminatorType.STRING, length=16)
@DiscriminatorValue( value="BOOK")
@ManagedBean( name="book") @ViewScoped
public class Book {
  @Id @Column( length=10)
  @NotNull( message="An ISBN is required!")
  @Pattern( regexp="\\b\\d{9}(\\d|X)\\b", 
      message="The ISBN must be a ...!")
  private String isbn;
  @Column( nullable=false) @NotNull( message="A title is required!")
  private String title;
  @Column( nullable=false) @NotNull( message="A year is required!")
  @UpToNextYear
  @Min( value=1459, message="The year must not be before 1459!")
  private Integer year;
  // constructors, set, get and other methods
  ...
}

When using the JPA Single Table Inheritance technique for a class hierarchy, the @Inheritance annotation must be set for the superclass (e.g, Book in our example). It provides the strategy parameter with the value InheritanceType.SINGLE_TABLE. The @DiscriminatorColumn annotation specifies the name of the table column storing the values that discriminate between different types of subclasses (TextBook and Biography, in our example).

Unfortunately, we cannot use our BookCategoryEL enumeration for defining the type of the discriminator column, because Java expressions like BookCategoryEL.TEXTBOOK.name() are not constant, so a Java compiler exception is thrown. Instead, we use DiscriminatorType.STRING. The @DiscriminatorValue annotation specifies a unique value to be stored in the discriminator column of table rows representing an instance of the given class.

Further, we define the subtypes TextBook and Biography:

@Entity @Table( name="textbooks")
@DiscriminatorValue( value="TEXTBOOK")
@ManagedBean( name="textBook") @ViewScoped
public class TextBook extends Book {
  @Column( nullable=false) @NotNull( 
      message="A subject area value is required")
  String subjectArea;
  // constructors, set, get and other methods
  ...
}

TextBook and Biography are subclasses of Book, so we define them with extends Book. For each subclass, we add a @DiscriminatorValue annotation with a value from the BookCategoryEL enumeration.

2.4. STI database schema

As a result of the annotation

@Inheritance( strategy=InheritanceType.SINGLE_TABLE)

only one database table is used for representing a class hierarchy. This table contains the columns corresponding to all the properties of all the classes from the hierarchy plus the discriminator column. In our example, the books table contains the following columns: isbn, title, year, subjectArea, about and category. The simplified corresponding SQL-DDL code internally used to generate the books table for our application is shown below:

CREATE TABLE IF NOT EXISTS `books` (
  `ISBN` varchar(10) NOT NULL,
  `TITLE` varchar(255) NOT NULL,
  `YEAR` int(11) NOT NULL,
  `SUBJECTAREA` varchar(255) DEFAULT NULL,
  `ABOUT` varchar(255) DEFAULT NULL,
  `category` varchar(16) DEFAULT NULL,
) 

2.5. Write the view and controller code

The user interface (UI) consists of a start page that allows navigating to the data management pages (in our example, to index.xhtml in the folder WebContent/views/books/). Such a data management page contains four sections: retrieve/list all books, create book, update book and delete book.

2.5.1. Summary

We have to take care of handling the category discriminator and the subjectArea and about segment properties both in the "List all books" use case as well as in the "Create book" and "Update book" use cases by

  1. Adding a segment information column ("Special type") to the display table of the Retrieve/List All use case in retrieveAndListAll.xhtml in the folder WebContent/views/books/.

  2. Adding a "Special type" select control, and corresponding form fields for all segment properties, in the forms of the "Create book" and "Update book" use cases in create.xhtml and update.xhtml in the folder WebContent/views/books/. Segment property form fields are only displayed, and their validation occurs only when a corresponding book category has been selected.

2.5.2. Add a segment information column in Retrieve/List All

We add a "Special type" column to the display table of the "List all books" use case in books.html:

<h:dataTable value="#{bookController.books}" var="b">
  ...
  <h:column>
    <f:facet name="header">Special type</f:facet>
    #{b.getClass().getSimpleName() != 'Book' ? 
        b.getClass().getSimpleName() : ''}
  </h:column>
</h:dataTable>

A conditional expression is used to check if the Java bean class name is Book, in which case we don't show it, or if it is something else (e.g. TextBook or Biography), and then it is shown. This expression also shows how you can call/use various Java bean methods, not only custom methods.

2.5.3. Add a "Special type" select control in Create and Update

In both use cases, we need to allow selecting a special category of book ('textbook' or 'biography') with the help of a select control in a panel grid element:

<h:panelGrid id="bookPanel" columns="3">
  ...
  <h:outputText value="Special type: " />
  <h:selectOneMenu id="bookType" value="#{viewScope.bookTypeVal}">
    <f:selectItem itemLabel="---"/>
    <f:selectItems value="#{book.typeItems}" />
    <f:ajax event="change" execute="@this" 
      render="textBookPanel biographyBookPanel standardBookPanel"/>
  </h:selectOneMenu>
  <h:message for="bookType" errorClass="error" />
</h:panelGrid>

In the select control, three alternative panel groups are referenced:

  1. A "standardBookPanel":

    <h:panelGroup id="standardBookPanel">  
      <h:commandButton value="Create" 
           rendered="#{viewScope.bookTypeVal == null}"
           action="#{bookController.create( book.isbn, 
               book.title, book.year)}" />
    </h:panelGroup>
  2. A "textBookPanel":

    <h:panelGroup id="textBookPanel">  
      <h:panelGrid rendered="#{viewScope.bookTypeVal == 'TEXTBOOK'}" 
           columns="3">
        <h:outputText value="Subject area: " />
        <h:inputText id="subjectArea" value="#{textBook.subjectArea}"/>
        <h:message for="subjectArea" errorClass="error" />
      </h:panelGrid>
      <h:commandButton value="Create" 
           rendered="#{viewScope.bookTypeVal == 'TEXTBOOK'}"
           action="#{textBookController.create( book.isbn, book.title, 
               book.year, textBook.subjectArea)}" />
    </h:panelGroup>
  3. A "biographyBookPanel":

    <h:panelGroup id="biographyBookPanel">  
      <h:panelGrid rendered="#{viewScope.bookTypeVal == 'BIOGRAPHY'}" 
           columns="3">
        <h:outputText value="About: " />
        <h:inputText id="about" value="#{biographyBook.about}"/>
        <h:message for="about" errorClass="error" />
      </h:panelGrid>
      <h:commandButton value="Create" 
           rendered="#{viewScope.bookTypeVal == 'BIOGRAPHY'}"
           action="#{biographyBookController.create( book.isbn,  
               book.title, book.year, biographyBook.about)}" />
    </h:panelGroup>

There are a few important remarks on the above code:

  • the h:selectOneMenu is used to create a single selection list which is populated with book titles by using the getTypeItems method of the Book class. A more detailed explanation is presented in Part 3 enumeration app.

  • it is possible to conditionally render facelet components by using the rendered attribute. The JSF EL expression must return true or false, this making the HTML resulting elements to be part of the HTML DOM or not. Notice that the conditional expressions are evaluated in the server side. This is the method we use to hide or show the input form elements corresponding to various book types (e.g., TextBook has a subjectArea property while Biography has an about property).

  • the render attribute used with f:ajax specifies which of the JSF components are to be updated . This is needed because of the live DOM changes (client side, not server side) which applies after the AJAX call.

  • AJAX is used to submit the form for reevaluation when the special type selection list field is changed (something is selected). As a result, it enforces the rendering of the three panels corresponding to three book cases: simple book, text book and biography. Using execute="@this" we enforce the re-evaluation of the form at server side, so the resulting HTML DOM structure contains the changes accordiong to the conditions specified by the rendered attributes of the various JSF elements. Notice that a JSF element that has a conditional rendered expression must be a child of another JSF element which is always part of the DOM.

  • h:panelGroup is used to define a set of elements which are either shown or hidden.

  • the action attribute of the h:commandButton can't be used with conditional expressions, therefore we have to create three command buttons, one for each case: create/update a Book, a TextBook or a Biography. The create method of the corresponding controller class (i.e., BookController, TextBookController or BiographyController is called).

  • since we do not have a corresponding property in the Java bean class(es) for the special type (category), we can use JSF variables to store the value of the single select list, and then use the variable in rendered conditions for various elements. Therefore, for the h:selectOneMenu, the value="#{viewScope.bookTypeVal}" specifies that we use a "on the fly" defined property named bookTypeVal, which is part of the view scope internal JSF object(s). We can also define such variable outside the view scope, e.g., value="#{bookTypeVal}", but in this case they are request scoped, so their value is lost after submitting the form, and the rendered conditions can't be correctly evaluated.

For a class in the class hierarchy, one corresponding controller class is defined. It contains the specific create, update and delete methods. The shared methods, such as getAllObjects are defined by the controller of the top level class (e.g., for our example, this is BookController). See minimal app for more details on how to implement the controller class and the corresponding CRUD methods.

The Update Book test case is very similar with the Create Book test case. The Delete Book test case remains unchanged, see See minimal app for more details.