4. Write the Model Code

The Entity class model shown on the right hand side in Figure 9.1 can be coded step by step for getting the code of the entity classes of our Java EE web app.

4.1. Type mapping

When defining the properties, we first need to map the platform-independent datatypes of the information design model to the corresponding implicit Java supported datatypes according to the following table.

Table 9.2. Datatype mapping to Java

Platform-independent datatype Java datatype
String String
Integer int, long, Integer, Long
Decimal double, Double, java.math.BigDecimal
Boolean boolean, Boolean
Date java.util.Date

Notice that for precise computations with decimal numbers, the special datatype java.math.BigDecimal is needed.

A second datatype mapping is needed for obtaining the corresponding MySQL datatypes:

Table 9.3. Datatype mapping to MySQL

Platform-independent datatype MySQL datatype
String VARCHAR
Integer INT
Decimal DECIMAL
Boolean BOOL
Date DATETIME or TIMESTAMP

4.2. Code the constraints as annotations

In this section we add JPA constraint annotations and Bean Validation annotations for implementing the property constraints defined for the Book class in the Java entity class model. For the standard identifier attribute isbn, we add the JPA constraint annotations @Id and @Column( length=10), as well as the Bean Validation annotations @NotNull and @Pattern( regexp="\\b\\d{10}\\b"). Notice that, for readability, we have simplified the ISBN pattern constraint.

For the attribute title, we add the JPA constraint annotation @Column( nullable=false), as well as the Bean Validation annotations @NotNull and @Size( max=50).

For the attribute year, we add the JPA constraint annotation @Column( nullable=false), as well as the Bean Validation annotations @NotNull and @Min( 1459). Notice that we cannot express the constraint that year must not be greater than next year with a standard validation annotation. Therefore, we'll define a custom annotation for this constraint in Section 6 below.

Coding the integrity constraints with JPA constraint annotations and Bean Validation annotations results in the following annotated bean class:

@Entity @Table( name="books")
@ManagedBean( name="book") @ViewScoped
public class Book {
  @Id @Column( length=10)
  @NotNull( message="An ISBN value is required!")
  @Pattern( regexp="\\b\\d{10}\\b", 
      message="The ISBN must be a 10-digit string!")
   private String isbn;
  @Column( nullable=false)
  @NotNull( message="A title is required!")
  @Size( max=255)
   private String title;
  @Column( nullable=false)
  @NotNull( message="A year is required!")
  @Min( value=1459, message="The year must not be before 1459!")
   private Integer year;

  ...  // define constructors, setters and getters 

  public static Book getObjectByStdId(...) {...}
  public static List<Book> getAllObjects(...) {...}
  public static void create(...) throws Exception {...}
  public static void update(...) throws Exception {...}
  public static void delete(...) throws Exception {...}
}

Notice that for the year property, the Java Integer wrapper class is used instead of the primitive int datatype. This is required for the combined use of JSF and JPA, because if the value of an empty year input field is submitted in the create or update forms, the value which is passed to the year property by JSF via the setYear method is null (more details on Section 4.5, “Requiring non-empty strings”), which is not admitted for primitive datatypes by Java.

We only provide an overview of the methods. For more details, see Chapter 4.

4.3. Checking uniqueness constraints

For avoiding duplicate Book records we have to check that the isbn values are unique. At the level of the database, this is already checked since the isbn column is the primary key, and the DBMS makes sure that its values are unique. However, we would like to check this in our Java app before the data is passed to the DBMS. Unfortunately, there is no predefined Bean Validation annotation for this purpose, and it is not clear how to do this with a custom validation annotation. Therefore we need to write a static method, Book.checkIsbnAsId, for checking if a value for the isbn attribute is unique. This check method can then be called by the controller for validating any isbn attribute value before trying to create a new Book record. The Book.checkIsbnAsId method code is shown below:

public static void checkIsbnAsId( EntityManager em, String isbn)
    throws UniquenessConstraintViolation, 
    MandatoryValueConstraintViolation {
  if (isbn == null) {
    throw new MandatoryValueConstraintViolation(
        "An ISBN value is required!");
  } else {
    Book book = Book.retrieve( em, isbn);
    // book was found, uniqueness constraint validation failed
    if ( book != null) {
    throw new UniquenessConstraintViolation(
        "There is already a book record with this ISBN!");
  }
  }
}

The method throws a UniquenessConstraintViolation exception in case that a Book record was found for the given ISBN value. The exception can then be caught and a corresponding error message displayed in the UI. In the sequel of this chapter we show how to define the controller validation method and inform JSF facelets that it must be used to validate the isbn form input field.

Notice that in this case we also need to check the isbn value and reject null values, because the @NotNull validation triggers only later, when the isbn property of the Book is set, thus at this point we could get NullPointerException, from the Book.retrieve method.

4.4. Dealing with model-related exceptions

The Book.checkIsbnAsId method discussed in the previous sub-section is designed to be used in combination with a controller so the user gets an error message when trying to duplicate a Book record (i.e., if the provided isbn value is already used in an existing record). However, if the Book.create method is used directly (i.e. by another piece of code, where the uniqueness constraint is not performed by calling Book.checkIsbnAsId), then uniqueness constraint validation may fail. Lets have a look on the Book.create code:

public static void create( EntityManager em, UserTransaction ut,
    String isbn, String title, int year) 
    throws NotSupportedException, SystemException, 
      IllegalStateException, SecurityException,
      HeuristicMixedException, HeuristicRollbackException, 
      RollbackException, EntityExistsException {
  ut.begin();
  Book book = new Book( isbn, title, year);
  em.persist( book);
  ut.commit();
}

The method may throw a number of exceptions when trying to execute the persist or the commit method. One of the exceptions (i.e. EntityExistsException) is thrown by the ut.commit call. The method which calls Book.create may catch this exception and perform specific actions, such as rolling back the transaction. In our case, the Book.create is called by the create action method of the BookController class, and the action performed is to show the exception stack trace in the console, as well as calling the ut.rollback which takes care of cancelling any database change performed by the current transaction. The rest of the exceptions are caught by using their super class (i.e. Exception) and the exception stack trace is displayed in the console.

public String create( String isbn, String title, int year) {
  try {
    Book.create( em, ut, isbn, title, year);
  } catch ( EntityExistsException e) {
    try {
      ut.rollback();
    } catch ( Exception e1) {
      e1.printStackTrace();
    } 
    e.printStackTrace();
  } catch ( Exception e) {
    e.printStackTrace();
  } 
  return "create";
}

Note: the EntityExistsException is part of the javax.persistence package (i.e. javax.persistence.EntityExistsException). TomEE uses the Apache OpenJPA implementation of the JPA API, which means that the EntityExistsException class (and other exceptions classes too) are part of the org.apache.openjpa.persistence package. Therefore, using this exception with our code, requires to import org.apache.openjpa.persistence.EntityExistsException; instead of import javax.persistence.EntityExistsException; as well as adding the openjpa-xxx.jar (located in the lib subfolder of the TomEE installation folder) to the Java application class path for being able to have the code compiled with Eclipse or other IDE tools.

4.5. Requiring non-empty strings

Normally a mandatory string-valued attribute, such as title, requires a non-empty string, which is expressed in our model above by the range NonEmptyString. For treating empty strings as no value, the context parameter javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL must be set to true in web.xml:

<context-param>
  <param-name>
    javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL
  </param-name>
  <param-value>true</param-value>
</context-param>