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.