Question

I have a Hibernate project where a call to update() needs to compare the modified object in memory to the data that has already been saved to the database. For example, my business logic states that if a record is "effective" (the effective date is today or earlier), an update cannot change the effective date. In order to accomplish this, I have the following code (it's a little long and involved):

Manager

public class LogicManager {

  @Autowired
  SessionFactory sessionFactory

  private Session getSession() {
    return sessionFactory.getCurrentSession();
  }

  public MemberRecord findRecord(Integer id) {
    // << Code to check authorization >>
    return memberRecordDAO.findById(id);
  }

  public void updateRecord(MemberRecord record) {
    getSession().evict(record);
    MemberRecord oldRecord = memberRecordDAO.findById(record.getId());

    Date oldEffectiveDate = oldRecord.getEffectiveDate();
    if ( isEffective(oldEffectiveDate) && 
         !oldEffectiveDate.equals(record.getEffectiveDate)) {
      throw new IllegalArgumentException("Cannot change date");
    }

    // << Other data checks >>
    memberRecordDAO.update(record);
  }
}

DAO

public class MemberRecordDAO {
  @Autowired
  private SessionFactory sessionFactory;

  private Session getSession() {
    return sessionFactory.getCurrentSession();
  }

  public MemberRecord findById(Integer id) {
    return (MemberRecord)getSession()
             .getNamedQuery("findMemberById")
             .setInteger("id", id)
             .uniqueResult();
  }
}

Client Code

// ...
public void changeEffectiveDate(Integer recordId, Date newDate) {
  LogicManager manager = getBean("logicManager");

  MemberRecord record = manager.findById(recordId);
  record.setEffectiveDate(newDate);
  manager.updateRecord(record);
}

Before I added the evict() call in the Manager, I noticed that the manager was behaving in unexpected ways. In order to update a record, I'd first have to get that record by calling findById(), which would put the record into the Session cache. I'd make changes on that object, then call updateRecord() which would call findById() to get the (supposedly) persisted data. I realized that this second call to findById() would not look at the database data, but just pull the object from the cache. This would result in my oldEffectiveDate always being the same as my newly changed date, since record and oldRecord would be the exact same object.

To counteract this, I added the call to evict(), which I understood to mean that the object would be removed from the cache, forcing Hibernate to go to the database to get the MemberRecord. After I made that change, my MemberRecordDAO throws an exception when it calls uniqueResult(), which says AssertionFailed: possible nonthreadsafe access to session. When I run the debugger, I see that both LogicManager and MemberRecordDAO are using the same Session, which is what I thought was correct.

So, my questions:

  1. Is my thinking/algorithm correct? Is evict() the correct thing to do? Is there a better way? I am not too savvy on Sessions, caching or evict(). I want to make sure that this logic is correct before dealing with threading issues.
  2. Why is it that accessing the Session from the DAO is not threadsafe?
Was it helpful?

Solution 2

This was the solution that passed my tests, but it still seems a little gross to me:

Manager

  public void updateRecord(MemberRecord record) {
    MemberRecord oldRecord = record;
    record = record.clone();   //Added a clone() to MemberRecord
    getSession().evict(record);
    getSession().evict(oldRecord);
    getSession().refresh(oldRecord);
    // At this point, record has all of the new values, but none of the Hibernate
    //  data attached to it, due to the clone(). 
    // oldRecord is populated with the data currently in the database.

    Date oldEffectiveDate = oldRecord.getEffectiveDate();
    if ( isEffective(oldEffectiveDate) && 
         !oldEffectiveDate.equals(record.getEffectiveDate)) {
      throw new IllegalArgumentException("Cannot change date");
    }


    // << Other data checks >>
    memberRecordDAO.update(record);
  }

If this type of thing can be done cleaner, please tell me.

OTHER TIPS

The evict() approach will work, but I believe the 'preferred hibernate way of doing things' would be to use Session.merge(), as in:

public MemberRecord updateRecord(MemberRecord newRecord) {

    MemberRecord oldRecord = memberRecordDAO.findById(record.getId());

    Date oldEffectiveDate = oldRecord.getEffectiveDate();
    if ( isEffective(oldEffectiveDate) && 
     !oldEffectiveDate.equals(newRecord.getEffectiveDate)) {
      throw new IllegalArgumentException("Cannot change date");
    } else {
       MemberRecord merged = (MemberRecord) session.merge(newRecord);
       return merged;
    }
}

Just keep in mind that Session.merge() will update all of the fields of oldRecord with the values from newRecord.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top