The JPA entity class model can be directly encoded for getting the model layer code of our Java back-end app.
Compared to the unidirectional association app discussed in a previous tutorial, we have to deal with a number of new technical issues:
In the model code you now have to take care of maintaining the derived inverse reference properties by maintaining the derived (sets of) inverse references that form the values of a derived inverse reference property. This requires in particular that
whenever the value of a single-valued master reference property is initialized or
updated with the help of a setter (such as assigning a
reference to an Publisher
instance p
to
b.publisher
for a Book
instance b
), an
inverse reference has to be assigned or added to the corresponding value of the
derived inverse reference property (such as adding b
to
p.publishedBooks
); when the value of the master reference
property is updated and the derived inverse
reference property is multi-valued, then the
obsolete inverse reference to the previous value of the single-valued master
reference property has to be deleted;
whenever the value of an optional single-valued master reference property is
unset
(e.g. by assigning null
to b.publisher
for a
Book
instance b
), the inverse reference has to be
removed from the corresponding value of the derived inverse reference property
(such as removing b
from p.publishedBooks
), if the
derived inverse reference property is multi-valued, otherwise the corresponding
value of the derived inverse reference property has to be unset or
updated;
whenever a reference is added to the value of a multi-valued master reference property with the help
of an add method (such as adding an Author
reference a
to
b.authors
for a Book
instance b
), an inverse
reference has to be assigned or added to the corresponding value of the derived inverse
reference property (such as adding b
to a.authoredBooks
);
whenever a reference is removed from the value of a multi-valued master reference
property with the help of a remove
method (such as removing a
reference to an Author
instance a
from
b.authors
for a Book
instance b
), the
inverse reference has to be removed from the corresponding value of the derived
inverse reference property (such as removing b
from
a.authoredBooks
), if the derived inverse reference property is
multi-valued, otherwise the corresponding value of the derived inverse reference
property has to be unset or updated;
whenever an object with a single reference or with multiple references as
the value of a master reference property is destroyed (e.g., when a
Book
instance b
with a single reference
b.publisher
to a Publisher
instance p
is destroyed), the derived inverse refences have to be removed first (e.g., by
removing b
from p.publishedBooks
).
Notice that when a new object is created with a single reference or with multiple
references as the value of a master reference property (e.g., a new Book
instance b
with a single reference b.publisher
), its setter
or add method will be invoked and will take care of creating the derived inverse
references.
In the UI code we can now exploit the inverse reference properties for more efficiently creating a list of inversely associated objects in the list objects use case. For instance, we can more efficiently create a list of all published books for each publisher.
Encode each model class as a Java class.
Encode the property set and get methods.
Encode the add and remove methods.
Encode any other operation.
Take care of the inverse relations management in the add and update (static) methods.
These six steps are discussed in more detail in the following sections.
In the Publisher
class, we add the publishedBooks
property and we
use the @OneToMany
annotation corresponding to @ManyToOne
from the
Book
class, representing the inverse relation:
@Entity public class Publisher { ... @OneToMany( fetch=FetchType.EAGER, mappedBy="publisher") private Set<Book> publishedBooks; ... }
The mappedBy
parameter of the @OneToMany
annotation of the
Publisher::publishedBooks
property specifies the property that implements the
@ManyToOne
relation in the Book
class:
@Entity public class Book { ... @ManyToOne( fetch=FetchType.EAGER) @JoinColumn( name="PUBLISHER_NAME") private Publisher publisher; ... }
In the Author
class we add the authoredBooks
property with the
@ManyToMany
annotation corresponding to @ManyToMany
from the Book
class representing the inverse
relation:
@Entity public class Author { ... @ManyToMany( fetch=FetchType.EAGER, mappedBy="authors") private Set<Book> authoredBooks; ... }
The mappedBy
property of the @ManyToMany
annotation for
authoredBooks
property of the Author
class specifies
the property name which implements the @ManyToMany
relation in the
Book
class:
@Entity... public class Book { ... @ManyToMany( fetch=FetchType.EAGER) @JoinTable( name="books_authors", joinColumns = { @JoinColumn( name="BOOK_ISBN") }, inverseJoinColumns = { @JoinColumn( name="AUTHOR_PERSONID") }) private Set<Author> authors; ... }
We also use the @JoinTable
annotation to specify the join table name for the
Many-To-Many relation and the corresponding
colum names for the join table, e.g., the table is books_authors
and
the columns are named BOOK_ISBN
and
AUTHOR_PERSONID
.
Any setter for a reference property that is coupled to a derived inverse reference property
(implementing a bidirectional association), now also needs to assign add/remove
inverse references to/from the corresponding value (set) of the inverse property. An
example of such a setter is the following setPublisher
method:
public class Book { ... public void setPublisher( Publisher publisher) { // remove the book from the publishedBooks // of the old publisher (if not null) if ( this.publisher != null) { this.publisher.removePublishedBook( this); } // add the book to publishedBooks // of the new publisher (if not null) if ( publisher != null) { publisher.addPublishedBook( this); } this.publisher = publisher; } }
For any multi-valued reference property that is coupled to a derived inverse reference property, both the add and the remove method also have to assign/add/remove corresponding references to/from (the value set of) the inverse property.
For instance, for the multi-valued reference property Book::authors
that is
coupled to the derived inverse reference property Author:authoredBooks
for
implementing the bidirectional authorship association between Book
and
Author
, the addAuthor
method is encoded in the following way:
public class Book { ... public void addAuthor( Author author) { if ( this.authors == null) { this.authors = new HashSet<Author>(); } if ( !this.authors.contains( author)) { // add the new author reference this.authors.add( author); // add the derived inverse reference author.addAuthoredBook( this); } } }
For the remove operation removeAuthor
we obtain the following code:
public class Book { ... public void removeAuthor( Author author) { if ( this.authors != null && author != null && this.authors.contains( author)) { // delete the author reference this.authors.remove( author); // delete the derived inverse reference author.removeAuthoredBook( this); } } }
Remember, for a Java Collection
, such as Set
or
List
, the contains
method compares two objects by using
the equals
method of the objects. For example, for using the
contains
method over a Set<Authors>
, such as
publishedBooks
, in the Author
class we implement the
following equals
method (two authors are equal if their
personId
property values are equal):
public class Author { ... @Override public boolean equals( Object obj) { if (obj instanceof Author) { Author author = (Author) obj; return ( this.personId.equals( author.personId)); } else return false; } ... }
When a Book
instance b
, with a single reference
b.publisher
to a Publisher
instance p
and
multiple references b.authors
to Author
instances, is
destroyed, the derived inverse references have to be removed first (e.g., by
removing b
from p.publishedBooks)
. This is accomplished by
calling the set methods for the single and multi-valued properties with a
null
parameter, e.g., b.setPublisher(null)
and
b.setAuthors(null)
within the Book.destroy
method:
public class Book { ... public static void destroy( EntityManager em, UserTransaction ut, String isbn) throws Exception { ut.begin(); Book b = em.find( Book.class, isbn); b.setPublisher( null); b.setAuthors( null); em.remove( b); ut.commit(); } }
In the same way, we have to take care of deleting the references with the Book
when deleting a Publisher
or an Author
instance:
public class Author { ... public static void destroy( EntityManager em, UserTransaction ut, Integer personId) throws Exception { ut.begin(); Author a = em.find( Author.class, personId); a.setAuthoredBooks( null); em.remove( a); ut.commit(); } } public class Publisher { public static void destroy( EntityManager em, UserTransaction ut, String name) throws Exception { ut.begin(); Publisher p = em.find( Publisher.class, name); p.setPublishedBooks( null); em.remove( p); ut.commit(); } }
Unfortunately, using JPA does not magically comes with all the inverse relation maintenance for our entities. According with Java Persistence Wiki, we have to implement the relations (direct and inverse) management as part of set/add/remove methods: A common problem with bidirectional relationships is the application updates one side of the relationship, but the other side does not get updated, and becomes out of sync. In JPA, as in Java in general, it is the responsibility of the application or the object model to maintain relationships. If your application adds to one side of a relationship, then it must add to the other side. This is commonly resolved through add or set methods in the object model that handle both sides of the relationships, so the application code does not need to worry about it. There are two ways to go about this: you can either add the relationship maintenance code to only one side of the relationship, and only use the setter from that side (such as making the other side protected), or add it to both sides and ensure you avoid an infinite loop.
We have used for each controller class an entity manager. Every entity manager maintains a
set of cached entities which can be "shared" with other entity managers by using the
merge
method. In our example code, as part of the add
and update
static methods of each model class, we have to merge the
updated entities which belongs to another entity manager. For example, for the in
the case of Book.add
method, we need to merge the
publisher
(managed by the entity manager used by the
PublisherController
class) and all the authors (managed by the
entity manager used by the AuthorController
class) which were
referenced in the new Book
instance:
public class Book { public static void add( EntityManager em, UserTransaction ut, String isbn, String title, Integer year, Publisher publisher, Set<Author> authors) throws Exception { ut.begin(); Book book = new Book( isbn, title, year, publisher, authors); em.persist( book); if ( publisher != null) { em.merge( publisher); } if ( authors != null) for ( Author a : authors) { em.merge( a); } } ut.commit(); } }
Without using merge
, the publisher
as well as all author instances
from the authors
list, does not represents references to the originally
cached entities, and are not going to retain our changes, e.g., when needs to be
reused later with other Book
instances. For more details, please check
the Java Persistence Documentation related to this matter.
It is also possible to disable the JPA caching, so the entities are reloaded from
database for every new instance, e.g., when EntityManager.find
method
is called. This can be achieved by adding the following line in the
persistence.xml
file:
<property name = "eclipselink.cache.shared.default" value = "false"/>
The above configuration works for eclipselink
impelementation and it
may be different for other API implementations. Notice that disabling entity caching
is not recommended without a serious reason, while it comes with performance loss
and may produce unpredicted behavior in some cases, such as using
@SessionScoped
managed entities.