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