The Java Entity class model can be directly coded for getting the model layer code of our Java back-end app.
Compared to the unidirectional association app discussed before, 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 references 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.
Code each class of the Java Entity class model as a corresponding entity class.
Code an {id} property modifier with the JPA annotation @Id
.
Code any property modifier denoting the functionality type of a reference property, such as {manyToOne}, with the corresponding JPA annotation, such as @ManyToOne
.
Code the integrity constraints specified in the model with the help of Java Bean Validation annotations (or custom validation annotations).
Code the getters and setters as well as the add and remove methods for multi-valued properties.
Code the create
, retrieve
, update
and delete
storage management operations as class-level methods.
Take care of the inverse relation management in the create
and update
methods.
These 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
attribute of the @ManyToMany
annotation for authoredBooks
reference property of the Author
class specifies the name of the inverse reference property 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 reference from publisher.publishedBooks if (this.publisher != null) { this.publisher.removePublishedBook( this); } // add the book to publisher.publishedBooks 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 coded 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.delete
method:
public class Book { ... public static void delete( 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 to Book
instances when deleting a Publisher
or an Author
instance:
public class Author { ... public static void delete( 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(); } }
And, likewise,
public class Publisher { public static void delete( 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, JPA does not provide automatic maintenance of derived inverse references. According to the Java Persistence Wiki, we have to implement the (direct and inverse) relations management within 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 an entity manager for each controller class. 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 create
and update
methods of each model class, we have to merge the updated entities that belong to another entity manager. For example, in the Book.create
method, we need to merge the new book's publisher
, managed by the PublisherController
's entity manager, and all its authors, managed by the AuthorController
's entity manager:
public class Book { public static void create( 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, do not represent references to the originally cached entities, and are not going to retain our changes. 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 the 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
implementation and it may be different for other API implementations. Notice that disabling entity caching is not recommended without a serious reason, since it degrades the app's performance and may produce unpredicted behavior in some cases, such as when using @SessionScoped
managed entities.