У меня есть проект 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);
}
Перед тем, как добавить в диспетчер вызов evict()
, я заметил, что менеджер ведет себя неожиданным образом.Чтобы обновить запись, мне сначала нужно получить эту запись, вызвав findById()
, что поместит запись в кэш сеанса.Я бы внес изменения в этот объект, а затем вызвал бы updateRecord()
, который вызвал бы findById()
, чтобы получить (предположительно) постоянные данные.Я понял, что этот второй вызов findById()
не будет смотреть на данные базы данных, а просто вытянет объект из кэша.Это приведет к тому, что мои oldEffectiveDate
всегда будут совпадать с моей новой измененной датой, поскольку record
и oldRecord
будут точно такими же объектами.
Чтобы противодействовать этому, ядобавил вызов к evict()
, который, как я понял, означал, что объект будет удален из кэша, заставив Hibernate перейти в базу данных, чтобы получить MemberRecord
.После того, как я сделал это изменение, мой MemberRecordDAO
выдает исключение, когда он вызывает uniqueResult()
, который говорит AssertionFailed: possible nonthreadsafe access to session
.Когда я запускаю отладчик, я вижу, что и LogicManager
, и MemberRecordDAO
используют один и тот же Session
, что я и считаю правильным.
Итак, мои вопросы:
- Правильно ли мое мышление / алгоритм?
evict()
правильная вещь?Есть ли способ лучше?Я не слишком разбираюсь в сессиях, кэшировании или evict()
.Я хочу убедиться, что эта логика верна, прежде чем решать проблемы с потоками. - Почему доступ к
Session
из DAO не является потокобезопасным?