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 second part of the tutorial we show how to express integrity constraints in a Java model class, and how to have constraints validated transparently on critical lifecycle events of bean objects (such as persist) with JPA, and on form submission with JSF.
We again consider the single-class data management problem that was considered in Part 1 of this tutorial. So, again, the purpose of our app is to manage information about books. But now we also consider the data integrity rules (also called 'business rules') that govern the management of book data. These integrity rules, or constraints, can be expressed in a UML class diagram as as shown in Figure 6.1 below.
In this simple model, the following constraints have been expressed:
Due to the fact that the isbn
attribute is declared to be the standard identifier of Book
, it is mandatory and unique.
The isbn
attribute has a pattern constraint requiring its values to match the ISBN-10
format that admits only 10-digit strings or 9-digit strings followed by "X".
The title
attribute is mandatory.
The year
attribute is mandatory and has an interval constraint, however, of a special form
since the maximum is not fixed, but provided by the calendaric function
nextYear()
.
Notice that the edition
attribute is not mandatory, but optional, as indicated by its multiplicity expression [0..1]. In addition to the
constraints described in this list, there are the implicit range constraints defined by assigning
the datatype NonEmptyString
as range to isbn
and title
,
Integer
to year
, and PositiveInteger
to
edition
.
The meaning of the design model can be illustrated by a sample data population for the model
class Book
:
Table 6.1. Sample data for Book
ISBN | Title | Year | Edition |
---|---|---|---|
006251587X | Weaving the Web | 2000 | 3 |
0465026567 | Gödel, Escher, Bach | 1999 | 2 |
0465030793 | I Am A Strange Loop | 2008 |
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/JPA/JSF apps support this goal to some degree. There are two types of constraint annotations:
Database constraint annotations, which are part of JPA, 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 database constraint annotations specify constraints to be used by the underlying
database 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 values of the itemCode
column allowed in the generated items
table
are mandatory and 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 Java's Bean Validation approach, Java runtime validation can be defined in the form of bean validation annotations placed on a property, method, or class.
Table 6.2. JavaBean 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 n the past (@Past
).
All bean validation annotations have an optional message
attribute for
defining a specific error message. In the following example, we add two @NotNull
annotations with messages, a @Size
and a @Min
annotation to the
database 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; }