The Java data model can be directly encoded for getting the code of the model layer of our Java back-end app.
Encode each model class as a Java class.
Add the validation constraints by using Java Validation API (or custom defined) validation annotations.
Encode the property setters.
Encode the add
, update
and destroy
operations
as (static-level) methods.
Encode any other operation.
These steps are discussed in more detail in the following sections.
For the multi-valued reference property Book::authors
, we use a
parametrized Set
type:
@Entity @Table( name="books") @ViewScoped @ManagedBean( name="book") public class Book { ... @ManyToMany( fetch=FetchType.EAGER) private Set<Author> authors; public Book() {} public Book( String isbn, String title, Integer year, Publisher publisher, Set<Author> authors) {...} ... public Set<Author> getAuthors() { return this.authors;} public void setAuthors( Set<Author> authors) { this.authors=authors;} ... }
The JPA annotation @ManyToMany
allows to specify the Many-To-Many relation between the Book
and Author
.
The annotation parameter FetchType.EAGER
is used, so when a Book
instance is created, the list of authors is populated with the corresponding
Author
instances. As a result of this annotation, a relation table between
Book
and Author
is created, and the resulting SQL code is shown
below:
CREATE TABLE IF NOT EXISTS `books_author` ( `Book_ISBN` varchar(10) NOT NULL, `authors_PERSONID` int(11) NOT NULL );
The resulting class name is the concatenation, underscore separated, of the
corresponding table names (e.g., books_author
). The primary key columns from each
of the two tables are used to implement the relation. The corresponding column names are created
as follows:
for the table (e.g., books
) which correspond to the class with the
@ManyToMany
annotation (e.g., Book
), the class name is used as
well as the primary key column name, (e.g., Book_ISBN
).
for the other table (e.g., authors
), the table name is concatenated with
the primary key column name, (e.g., authors_PERSONID).
It is possible to control these parameters, i.e., table name and relation column
names, by using the @JoinTable
annotation. To obtain a custom named relation table,
e.g., books_authors
and the corresponding custom named columns, e.g.,
book_isbn
and author_personid
, one can use:
@JoinTable( name="books_authors", joinColumns = {@JoinColumn( name="book_isbn", referencedColumnName="ISBN")}, inverseJoinColumns = {@JoinColumn( name="author_personid", referencedColumnName="PERSONID")}
In
our application, we keep the default, so a @JoinTable
annotation is not
used.
The corresponding Author class is encoded as a simple Java class with the corresponding JPA and Java Validation API annotations:
@Entity @Table( name="author") @ManagedBean( name="author") @ViewScoped public class Author { @Id @PositiveInteger private Integer personId; @Column( nullable=false) @NotNull( message="A name is required!") private String name; @Column( nullable=false) @NotNull( message="A date of birth is required!") @Past private Date dateOfBirth; @Past private Date dateOfDeath; public Author() {} public Author( Integer personId, String name, Date dateOfBirth, Date dateOfDeath) {...} public Integer getPersonId() {...} public void setPersonId( Integer personId) {...} public String getName() {...} public void setName( String name) {...} public Date getDateOfBirth() {...} public void setDateOfBirth( Date dateOfBirth) {...} public Date getDateOfDeath() {...} public void setDateOfDeath( Date dateOfDeath) {...} public static void add( EntityManager em, UserTransaction ut, Integer personId, String name, Date dateOfBirth, Date dateOfDeath) {...} public static void update( EntityManager em, UserTransaction ut, Integer personId, String name, Date dateOfBirth, Date dateOfDeath) {...} public static void destroy( EntityManager em, UserTransaction ut, Integer personId) {...} }
Custom validation annotations are defined and implemented (i.e.,
@PositiveInteger
) as shown in Part 2 (Validation Tutorial).
For the reference property Book::authors
, we have to implement a deletion
policy in the destroy
method of the Author
class. If we just try
to delete an author, and the author is referenced by any of the book records, then an
integrity constraint violation fires, and the author cannot be deleted. We have two
possiblitities for this situation:
delete all books (co-)authored by the deleted author;
drop from all books (co-)authored by the deleted author the reference to the deleted author.
We go for the second option. This is shown in the following code of the
Author.destroy
method:
public static void destroy( EntityManager em, UserTransaction ut, Integer personId) throws Exception { ut.begin(); Author author = em.find( Author.class, personId); // find all books with this author Query query = em.createQuery( "SELECT DISTINCT b FROM Book b "+ "INNER JOIN b.authors a WHERE a.personId = :personId"); query.setParameter( "personId", personId); List<Book> books = query.getResultList(); // update the corresponding book-to-author relations from the association // table (otherwise the author can't be deleted) for ( Book b : books) { b.getAuthors().remove( author); } // remove the author entry (table row) em.remove( author); ut.commit(); }
Essentially, there are three steps for this operation:
create a JPQL query which allows to select all book instances for this author - remember, this is an unidirectional association, there is no direct method available for this case.
for every found book which reference this author, we have to remove the author from its authors list.
remove tha author - now is safe and no relation specific error should occur.
In Java, by using the JPA built-in annotations (i.e., @Entity
for the class
and the corresponding ones for the properties) together with converter classes where is the
case as shown in Part 3 (Eumeration
Tutorial), the serialization is made internally. This means, the serialization from
Java object to the corresponding database table row as well as the de-serilaization from
database table column to Java object are made automatically.