сегодня я написал тестовый пример для своего приложения, чтобы увидеть, как ведут себя транзакции. И я обнаружил, что ничего не работает так, как я думал.
У меня есть приложение на основе Spring, использующее Hibernate в качестве поставщика JPA, при поддержке MySQL.
У меня есть объекты DAO, расширяющие Spring JpaDaoSupport. На них распространяется управление транзакциями Spring.
Созданный мной тестовый пример работает так:
1) Сущность создана с некоторым счетчиком, установленным в 0.
2) Затем создаются два потока, которые оба вызывают метод DAO incrementCounter () в цикле.
Я думал, что когда методы DAO покрыты транзакцией, тогда в ней будет только один поток (т.е. Spring позаботится о синхронизации). Но это оказалось ложным предположением.
После (временного) решения этой проблемы путем добавления synchronized
к методу DAO я обнаружил, что Hibernate не хранит изменения, сделанные методом DAO, а другой поток, когда находя () объект, имеет старый данные. Помог только явный вызов this.getJpaTemplate().flush();
.
Я также думал, что менеджер сущностей выдаст мне тот же экземпляр сущности из кэша контекста постоянства, но это также неверно. Я проверил hashCode () и equals (), и они в порядке - на основе бизнес-ключа сущности.
Любые комментарии приветствуются, так как мне кажется, что мне не хватает некоторых базовых понятий о том, как JPA / Spring работает с транзакциями.
- Должны ли методы DAO быть
synchronized
?
- Должен ли я вызывать flush () в конце каждого метода DAO?
- Является ли Spring ответственным за то, что вызовы методов DAO ведут себя транзакционно? (т.е. не позволяя двум потокам работать с одним и тем же объектом одновременно)
- Если нет, то как мне этого добиться?
Обратите внимание, что в моем тестовом примере я использую один объект DAO, но это должно быть нормально, поскольку бины Spring являются синглетонами - верно?
Спасибо за любую помощь.
public class EntityDaoImpl extends JpaDaoSupport implements EntityDao {
public synchronized void incrementCounter( String znacka )
{
String threadName = Thread.currentThread().getName();
log.info(threadName + " entering do incrementCounter().");
Entity ent = this.getJpaTemplate().find( Entity.class, znacka );
log.info("Found an entity "+ent.getZnacka()+"/"+ent.hashCode()+" - " + ObjectUtils.identityToString( ent ) );
log.info(threadName + ": Actual count: "+ent.getCount() );
ent.setCount( ent.getCount() + 5 );
int sleepTime = threadName.endsWith("A") ? 700 : 50;
try { Thread.sleep( sleepTime ); }
catch( InterruptedException ex ) { }
ent.setCount( ent.getCount() + 5 );
this.getJpaTemplate().flush();
log.info(threadName + " leaving incrementCounter().");
}
}
Без synchronized
и flush()
, это давало мне вывод как
Thread A: Actual count: 220
...
Thread B: Actual count: 220
...
Thread A: Actual count: 240
...
Thread B: Actual count: 250
...
Thread A: Actual count: 250
... и т. Д., Что означает, что один поток перезаписал изменения другого.