5. Write the Model Code

The JavaBeans data model shown on the right hand side in Figure 6.2 can be encoded step by step for getting the code of the model classes of our Java web app.

5.1. Type mapping

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

Table 6.3. 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 correspnding MySQL data types:

Table 6.4. Datatype mapping to MySQL

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

5.2. Encode the constraints as annotations

In this section we add database constraint annotations and bean validation annotations for implementing the property constraints defined for the Book class in the JavaBean data model. For the standard idenitfier attribute isbn, we add the database 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 readbilty, we have simplified the ISBN pattern constraint.

For the attribute title, we add the database constraint annotation @Column( nullable=false), as well as the bean validation annotations @NotNull and @Size( max=255).

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

Encoding the integrity constraints with database 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 add(...) throws Exception {...}
  public static void update(...) throws Exception {...}
  public static void destroy(...) throws Exception {...}
}

Notice that for the year property, the Java Integer wrapper class is used instead of the primitive int data type. 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 5.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 our minimal app tutorial.

5.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 perform this check in our Java app before the data is passed to the DBMS and create suitable error messages. Unfortunatelly, 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 {
  Book book = Book.getObjectByStdId( em, isbn);
  if ( book != null) {  // book was found, so isbn is not unique
    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 tutorial 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.

5.4. Dealing with model related exceptions

The Book.checkIsbnAsId method from the above section is designed to be used in combination with a controller so the user gets an error message when trying to duplicate a Book entry (i.e. the provided isbn value already exists). However, if the Book.add 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.add code:

public static void add( 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 throws a set of exceptions to reflect problems occurred when trying to run 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.add may catch this exception and perform specific actions, such as rolling back the transaction. In our case, the Book.add is called by the add action method of the BookController class, and the action performed is to show the exception track stace 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 catched by using their super class (i.e. Exception) and the exception track is displayed in the console.

public String add( String isbn, String title, int year) {
  try {
    Book.add( 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 use 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 classpath for being able to have the code compiled with Eclipse or other IDE tools.

5.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 null., 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>