我有一个Hibernate项目,其中对update()
的调用需要将内存中的修改对象与已保存到数据库的数据进行比较。例如,我的业务逻辑表明如果记录是有效的" (生效日期为今天或更早),更新无法更改生效日期。为了实现这一点,我有以下代码(它有点长并涉及):
管理器
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();
}
}
客户代码
// ...
public void changeEffectiveDate(Integer recordId, Date newDate) {
LogicManager manager = getBean("logicManager");
MemberRecord record = manager.findById(recordId);
record.setEffectiveDate(newDate);
manager.updateRecord(record);
}
在我在Manager中添加evict()
调用之前,我注意到管理器的行为方式出乎意料。为了更新记录,我首先必须通过调用findById()
来获取该记录,这将把记录放入会话缓存中。我对该对象进行了更改,然后调用updateRecord()
来调用findById()
以获取(据称)持久化数据。我意识到第二次调用findById()
将不查看数据库数据,而只是从缓存中提取对象。这会导致我的oldEffectiveDate
始终与我新更改的日期相同,因为record
和oldRecord
将是完全相同的对象。
为了抵消这一点,我添加了对evict()
的调用,我理解这意味着该对象将从缓存中删除,迫使Hibernate转到数据库以获取MemberRecord
。完成更改后,我的MemberRecordDAO
会在调用uniqueResult()
AssertionFailed: possible nonthreadsafe access to session
时抛出异常。当我运行调试器时,我发现LogicManager
和MemberRecordDAO
都使用相同的Session
,这是我认为正确的。
所以,我的问题:
evict()
是正确的做法吗?有没有更好的办法?我对Sessions,缓存或evict()
都不太了解。我想在处理线程问题之前确保这个逻辑是正确的。Session
不是线程安全的?答案 0 :(得分:2)
evict()方法可行,但我相信'首选的hibernate做事方式'是使用Session.merge(),如:
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;
}
}
请记住,Session.merge()将使用newRecord中的值更新oldRecord的所有字段。
答案 1 :(得分:0)
这是通过我的测试的解决方案,但它对我来说似乎有点粗糙:
<强>管理器强>
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);
}
如果这种类型的东西可以做得更干净,请告诉我。