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