2. Write the Model Code

The Java Entity class model can be directly coded for getting the model layer code of our Java back-end app.

2.1. New issues

Compared to the unidirectional association app discussed before, 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 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.

  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. Code each class of the Java Entity class model as a corresponding entity class.

  2. Code an {id} property modifier with the JPA annotation @Id.

  3. Code any property modifier denoting the functionality type of a reference property, such as {manyToOne}, with the corresponding JPA annotation, such as @ManyToOne.

  4. Code the integrity constraints specified in the model with the help of Java Bean Validation annotations (or custom validation annotations).

  5. Code the getters and setters as well as the add and remove methods for multi-valued properties.

  6. Code the create, retrieve, update and delete storage management operations as class-level methods.

  7. Take care of the inverse relation management in the create and update methods.

These steps are discussed in more detail in the following sections.

2.3. Code each class of the Java Entity class model

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.

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

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

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

2.7. Entity Managers and Cached Entities

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.