2. Write the Model Code

How to Encode a JPA Entity Class Model

The JPA entity class model can be directly encoded for getting the model layer code of our Java back-end app.

2.1. New issues

Compared to the unidirectional association app discussed in a previous tutorial, we have to deal with a number of new technical issues:

  1. 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

    1. 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;

    2. 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;

    3. 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);

    4. 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;

    5. 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.

  2. 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.

2.2. Summary

  1. Encode each model class as a Java class.

  2. Encode the property set and get methods.

  3. Encode the add and remove methods.

  4. Encode any other operation.

  5. 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.

2.3. Encode each class of the data model as an entity class

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.

2.4. Encode the setter operations

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;
  }
}

2.5. Encode the add and remove operations

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;
  }
  ...
}

2.6. Take care of deletion dependencies

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();
  }
}

2.7. EntityManagers and Cached Entities

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.