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.