Monday, August 15, 2016

Updating Hibernate JPA entity collection

A detached entity (a.k.a. a detached object) is an object that has the same ID as an entity in the persistence storage but that is no longer a part of a persistence context (the scope of an EntityManager session). The two most common causes for this are:
• The transaction, in which the object was created, is closed.
• The object was received from outside as a part of a form submission, a remote protocol such as REST etc.

It is a quite common case, when an existing entity update is implemented with a detached object like this:
 @Transactional
 public void save(User updatedUser){
    em.merge(updatedUser);
 }
An argument updatedUser here is a detached object. The EntityManager merge takes care to apply all changes from the updatedUser into the persistent storage instance. The changes are effective after transaction commit.

Another way to update the instance in DB is to get an object from EntityManager and change the object data. In this case it is not necessary to call merge. The object, which was changed, just will be saved on transaction commit. The general idea of such update is like below:
 @Transactional
 public void updateUser(int userId){
    User existingUser = em.find(userId);
    existsingUser.setWhateverProperty(propertyValue);
    ...
 }

Entity collection update


If a user model contains only properties of primitive types or single objects, both update methods bring the same result.
But in case a property is of a collection type, the first method (merge of detached object) should not be used.
This is due to EntityManager implementation for collections merge:
• If a collection in a detached object is empty, all entries will be deleted from the persistent entity collection.
• If a collection in a detached object contains entries, EntityManager copies all of them into the persistent entity collection. Depending on schema and Java object implementation one of the following problems will happen: entities duplication in the collection or constraint violation.
Update of a collection property should be implemented with explicit merge.

It does not matter, what type of a collection is used: an embedded collection, declared with @EmbeddedCollection or a result of tables join declared with @JoinTable and @OneToMany or other type.

Example of collection merge implementation


1. The demo DB stores passwords history for each user. It is defined with the scheme:
 CREATE TABLE `DEMO_DB`.`USERS` (  
  `ID` int(11) unsigned NOT NULL auto_increment,  
  `EMAIL` varchar(60) NOT NULL,  
  `LAST_NAME` varchar(60) NOT NULL,  
  `FIRST_NAME` varchar(30) NOT NULL,  
  CONSTRAINT `PRIMARY` PRIMARY KEY (`ID`),  
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
 CREATE TABLE `DEMO_DB`.`PASSWORD_HISTORY` (  
  `ID` varchar(60) NULL,  
  `USER_ID` int(11) unsigned NOT NULL,  
  `PASSWORD` varchar(256) NOT NULL,
  `IS_ACTIVE` tinyint(1) NOT NULL DEFAULT 1,  
  CONSTRAINT `UQ_PASSWORD_HISTORY_USER_ID` UNIQUE KEY `UQ_PASSWORD_HISTORY_USER_ID`(`USER_ID`,`USER`),  
  CONSTRAINT `FK_PASSWORD_HISTORY_USER_ID` FOREIGN KEY (`USER_ID`) REFERENCES `DEMO_DB`.`USERS` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE  
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  

2. The tables are mapped to JPA entities with the classes DBUser and DbPasswordHistory.
A password value makes sense only in a context of a specific user, that is why a collection of DbPasswordHistory is embedded in the DBUser.
The DBUser has the method merge, which implements explicit update of DBUser.
 @Table(name="USERS")  
 @Entity  
 @Access(AccessType.PROPERTY)  
 public class DbUser {  
      private int id;  
      private String email;  
      private String firstName;  
      private String lastName;  
      private Set<DbPasswordHistory> passwordHistory = new HashSet<DbPasswordHistory>();  
      @Id  
      @GeneratedValue  
      @Column(name="ID")  
      public int getId() {  
           return id;  
      }  
      @Column(name="EMAIL", nullable=false)  
      public String getEmail() {  
           return email;  
      }  
      @Column(name="FIRST_NAME", nullable=false)  
      public String getFirstName() {  
           return firstName;  
      }  
      @Column(name="LAST_NAME", nullable=false)  
      public String getLastName() {  
           return lastName;  
      }  
      @ElementCollection(fetch = FetchType.EAGER)  
      @CollectionTable(name="PASSWORD_HISTORY",joinColumns=@JoinColumn(name="USER_ID"))  
      public Set<DbPasswordHistory > getPasswordHistory () {  
           return passwordHistory ;  
      }  
      ...
      /**
       * The custom merge is needed to avoid duplication of embedded collection
       * 
       * @param other
       */
      public void merge(DbUser other) {  
           setEmail(other.getEmail());  
           setFirstName(other.getFirstName());  
           setLastName(other.getLastName());  
           setPasswordHistory(other.getPasswordHistory());  
      }  
 }

 @Embeddable  
 @Access(AccessType.FIELD)  
 public class DbPasswordHistory{  
      @Column(name="ID")  
      private String id;  
      @Column(name="PASSWORD", nullable=false)  
      private Password password;  
      @Column(name="IS_ACTIVE", nullable=false)  
      private boolean hostRegistered;  
      ...  
      public boolean isHostRegistered() {  
           return hostRegistered;  
      }  
      public void setHostRegistered(boolean hostRegistered) {  
           this.hostRegistered = hostRegistered;  
      }  

3. The class UserDAO implements update of DBUser entity:
 public class UserDAO {  
   ....  
    public void updateUser(DbUser updatedUser, EntityManager em) {  
      DbUser existingUser = em.find(DbUser.class, updatedUser.getId());  
       if (existingUser != null) {  
           existingUser.merge(updatedUser);  
       }  
    }  
    .....  
 }  

Due to explicit merge any update of a user password history is properly saved in DB.


No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top