Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop Hibernate from updating collections when they have not changed

I have two entity beans defined as follows (unrelated stuff removed):

@Entity
@Table(...)
public class MasterItem implements java.io.Serializable {

  private Set<CriticalItems> criticalItemses = new HashSet<CriticalItems>(0);

  @OneToMany(fetch = FetchType.EAGER, mappedBy = "masterItem", orphanRemoval = true,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
    public Set<CriticalItems> getCriticalItemses() {
        return this.criticalItemses;
    }
}

CriticalItems is defined as follows:

@Entity
@Table(...)
public class CriticalItems implements java.io.Serializable {

    private MasterItem masterItem;

    @ManyToOne(fetch = FetchType.LAZY, optional = false,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE})
    @JoinColumn(name = "mi_item_id", nullable = false)
    public MasterItem getMasterItem() {
        return this.masterItem;
    }
}

And in my DAO code - I have these methods:

public MasterItem load(int id) {
    MasterItem results = (MasterItem) getSessionFactory().getCurrentSession()
        .get("com.xxx.MasterItem", id);

}

public void save(MasterItem master) {
    // master has been changed by the UI since it
    getSessionFactory().getCurrentSession().saveOrUpdate(master);
}

When I load a MasterItem, it loads correctly, and also loads the CriticalItems Set with data, as directed. Then, I send this data to my UI, and get an updated copy back, which I then try to persist. The user updates fields in the MasterItem object, but does not touch the CriticalItems Set or anything in it - it remains unmodified.

When my save() method is invoked, Hibernate is insisting on sending SQL updates for each item in the Set of CriticalItems, even though none of them have changed in any way.

After some digging, here's what I think is happening. When I do a saveOrUpdate(), Hibernate sees my MasterItem object is in a detached state, so it tries to reload it from disk. However, when doing so, it appears to be using a prepared statement (which was auto-created by Hibernate at start-up) and this prepared statement does not attempt to join to the CriticalItems data.

So Hibernate has my updated MasterItem object with a full Set of CriticalItems, but uses a MasterItem without collections as its "previousState" object. Thus, all CriticalItems get updated via SQL (not inserted, which is interesting in itself).

Did I do something in my annotations that caused this behavior? I know I can use an Interceptor to find out of the item has really changed, or changed the dirty flag to override Hibernate's default algorithm - but this seems to be something that Hibernate should just handle on its own without my intervention.

Any insight would be appreciated.

UPDATE: Based on comments, I think I understand the difference between saveOrUpdate() and merge(). I realize that saveOrUpdate() will result in either an SQL INSERT or an SQL UPDATE in all cases, and that merge, in theory, will only issue updates if the object has changed from it's persistent state, but in order to determine that, Hibernate has to reload the object first via SQL SELECT.

So, I thought I could just go back into my code and change saveOrUpdate() to merge() and it would work, but that wasn't quite the case.

When I used merge(), I was getting

org.springframework.orm.hibernate3.HibernateSystemException: could not initialize proxy - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session

but it worked fine if I changed back to saveOrUpdate().

I finally found out why - I didn't include CascadeType.MERGE in my @Cascade annotation (ugh). Once I fixed that, the exception went away.

like image 883
Gordon Avatar asked Jan 18 '11 15:01

Gordon


People also ask

How do you indicate Hibernate to not update the table?

Use updatable=false in Hibernate to avoid updating the column.

How does hibernate know when to update?

When you use . saveOrUpdate() Hibernate will check if the object is transient (it has no identifier property) and if so it will make it persistent by generating it the identifier and assigning it to session. If the object has an identifier already it will perform . update() .

Why is hibernate doing select before insert?

Hibernate is trying to determine if the object is transient or not, so is performing a SELECT before INSERT .

What is save and update method in hibernate?

The save() method INSERTs an object in the database. It will persist the given transient instance, first assigning a generated identifier. It returns the id of the entity created. The saveOrUpdate() calls either save() or update() on the basis of identifier exists or not.


1 Answers

That's the semantical difference between update() and merge().

From Christian Bauer and Gavin King's Java Persistence with Hibernate (I can't find clear explanation of this behaviour in the Hibernate docs):

The update() method forces an update to the persistent state of the object in the database, always scheduling an SQL UPDATE.
...
It doesn’t matter if the item object is modified before or after it’s passed to update().
...
Hibernate always treats the object as dirty and schedules an SQL UPDATE., which will be executed during flush.

On the other hand, merge() queries the database first, and doesn't perform update if state haven't changed.

So, if you want Hibernate to query the database first, you need to use merge() (though default behaviour of update() can be overriden by specifing @org.hibernate.annotations.Entity(selectBeforeUpdate = true) on your entities).

like image 125
axtavt Avatar answered Nov 15 '22 21:11

axtavt