Table of Contents
The minimal web app that we have discussed in Part 1 has been limited to support the minimum functionality of a data management app only. For instance, it did not take care of preventing the user from entering invalid data into the app's database. In this chapter, we show how to express integrity constraints in a Java EE model class (called entity class), and how to have constraints automatically validated on critical life cycle events of entity objects with JPA, and on form submission with JSF.
The integrity constraints of a distributed app have to be checked both in model classes and in the underlying database, and possibly also in the UI. However, this requirement for three-fold validation should not imply having to define the same constraints three times in three different languages: in Java, in SQL and in HTML5/JavaScript. Rather, the preferred approach is to define the constraints only once, in the model classes, and then reuse these constraint definitions also in the underlying database and in the UI. Java EE apps support this goal to some degree. There are two types of constraint annotations:
JPA constraint annotations specify constraints to be used for generating the database schema (with CREATE TABLE statements) such that they are then checked by the DBMS, and not by the Java runtime environment;
Bean Validation annotations specify constraints to be checked by the Java runtime environment
In this section we discuss how to use some of the predefined
constraint annotations and how to define a custom constraint annotation
for the year property of the Book class, since
its value has an upper bound defined by an expression ('next
year').
The JPA constraint annotations specify constraints to be used by
the underlying database management system after generating the database
schema, but not for Java validation. Consider the @Id
annotation in the following definition of an entity class
Item:
@Entity @Table( name="items") public class Item { @Id private String itemCode; private int quantity; ... // define constructors, setters and getters }
The @Id annotation of the itemCode
attribute is mapped to a SQL primary key declaration for this attribute in the corresponding
database table schema. As a consequence, the itemCode
column of the generated items table must have a value in
each row and these values have to be unique. However, these conditions are not
checked in the Java runtime environment. JPA generates the following
CREATE TABLE statement:
CREATE TABLE IF NOT EXISTS items ( itemCode varchar(255) NOT NULL PRIMARY KEY, quantity int(11) DEFAULT NULL )
Since nothing has been specified about the length of
itemCode strings, the length is set to 255 by default.
However, in our case we know that itemCode has a fixed
length of 10, which can be enforced by using the @Column
annotation, which has the following parameters:
name allows to specify a name for the column to
be used when the table is created (by default, the attribute name
of the entity class is used);
nullable is a boolean parameter that defines if
the column allows NULL values (by default, it is
true);
length is a positive integer, specifying the
maximum number of characters allowed for string values of that
column (by default, this is 255);
unique is a boolean parameter that defines if
the values of the column must be unique (by default, it is
false).
Using the @Column annotation, the improved Java/JPA
code of the model class is:
@Entity @Table( name="items") public class Item { @Id @Column( length=10) private String itemCode; @Column( nullable=false) private int quantity; ... // define constructors, setters and getters }
As a result, the generated CREATE TABLE statement now contains the
additional constraints expressed for the columns itemCode
and quantity:
CREATE TABLE IF NOT EXISTS items ( itemCode varchar(10) NOT NULL PRIMARY KEY, quantity int(11) NOT NULL )
In the Java EE Bean Validation approach, Java runtime validation can be defined in the form of bean validation annotations placed on a property, method, or class.
Table 9.1. Bean Validation annotations for properties
| Constraint Type | Annotations | Examples |
|---|---|---|
| String Length Constraints | @Size |
@Size( min=8, max=80) String message; |
| Cardinality Constraints (for arrays, collections and maps) | @Size |
@Size( min=2, max=3) List<Member>
coChairs; |
| Mandatory Value Constraints | @NotNull |
@NotNull String name |
| Range Constraints for numeric attributes | @Digits |
@Digits( integer=6, fraction=2) BigDecimal
price; |
| Interval Constraints for integer-valued attributes | @Min and @Max |
@Min(5) int teamSize |
| Interval Constraints for decimal-valued attributes | @DecimalMin and @DecimalMax |
@DecimalMax("30.00") double voltage |
| Pattern Constraints | @Pattern |
@Pattern( regexp="\\b\\d{10}\\b") String
isbn; |
In addition, there are annotations that require a date value to be
in the future (@Future ) or in the past
(@Past).
All Bean Validation annotations have an optional
message attribute for defining a custom error message. In
the following example, we add two @NotNull annotations with
messages, a @Size and a @Min annotation to the
JPA constraint annotations. The @NotNull annotations
constrain the itemCode and the quantity
attributes to be mandatory, while the @Min annotation
constrains the quantity attribute to have a minimum value
of 0:
@Entity @Table( name="items") public class Item { @Id @Column( length=8) @NotNull( message="An item code is required!") @Size( min=8, max=8) private String itemCode; @Column( nullable=false) @NotNull( message="A quantity is required!") @Min(0) private int quantity; }
Notice that that we need some duplicate logic in this example because the same
constraints may have to be defined twice: as a JPA constraint annotation and as a Bean
Validation annotation. For instance, for a mandatory attribute like quantity we
have both a @Column( nullable=false) JPA constraint annotation and a
@NotNull Bean Validation annotation.