2009-08-24 2 views
1

сегодня я закодировал тестовый пример для своего приложения, чтобы посмотреть, как ведут себя транзакции. И я обнаружил, что ничего не работает так, как я думал.JPA и весенние транзакции - пожалуйста, объясните

У меня есть приложение Spring, использующее Hibernate как поставщик JPA, поддерживаемый MySQL. У меня есть объекты DAO, расширяющие JpaDaoSupport Spring. Они охвачены управлением транзакциями Spring.

Тестовый пример я создал как это работает: 1) Объект создается с некоторым счетчиком, установленным в 0. 2) Затем две нити созданы, которые оба вызова метода DAO incrementCounter() в петля.

Я думал, что когда методы DAO будут покрыты транзакцией, то внутри него будет только один поток (т. Е. Spring позаботится о синхронизации). Но это оказалось ложным предположением.

После (временного) решения этого вопроса путем добавления synchronized к методу DAO я обнаружил, что Hibernate не сохраняет изменения, внесенные методом DAO, а другой поток, когда find() принадлежит сущности, имеет старые данные , Помог только явный вызов this.getJpaTemplate().flush();.

Я также думал, что диспетчер объектов предоставит мне тот же экземпляр объекта из кеша контекста персистентности, но это также неверно. Я проверил hashCode() и equals(), и они прекрасны - на основе ключа бизнес-сущности.

Любые комментарии приветствуются, поскольку, кажется, мне не хватает некоторых базовых понятий о том, как JPA/Spring работает с транзакциями.

  1. Должны ли методы DAO быть synchronized?
  2. Должен ли я вызвать flush() в конце каждого метода DAO?
  3. Является ли весна ответственной за совершение звонков методам DAO с точки зрения транзакций? (то есть не позволяя двум потокам работать с одним и тем же объектом одновременно)
  4. Если нет, то как это сделать?

Обратите внимание, что в моем тестовом примере я использую один объект DAO, но это должно быть ОК, поскольку фасоль Весны - это одноточие - правильно?

Спасибо за любую помощь.

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 

... и т.д., а это означает, что одна нить перезаписаны изменения по сравнению с другой.

ответ

3
  1. Должны ли быть синхронизированы методы DAO? Обычно шахты не являются, потому что у них нет никакого состояния. Не нужно.
  2. Должен ли я вызвать flush() в конце каждого метода DAO? Нет, я этого не делаю.
  3. Является ли Весна ответственной за совершение звонков методам DAO с точки зрения транзакций? (то есть не позволяя двум потокам работать с одним и тем же объектом одновременно). Я думаю, вы запутываете транзакции, изоляцию и синхронизацию.
  4. Если нет, то как мне это достичь? Я думаю, вам нужно беспокоиться о синхронизации и изоляции.

Ваш пример - это не то, что я бы назвал DAO. Я думаю, что ваш тест действительно не является правильной идиомой. Если бы я был объект Foo, я должен был бы интерфейс FooDao что бы декларировать методы CRUD для этого объекта:

public interface FooDao 
{ 
    List<Foo> find(); 
    Foo find(Serializable id); 
    void saveOrUpdate(Foo foo); 
    void delete(Foo foo); 
} 

Можно написать общий DAO, как вы бы догадаться из примера.

Весна обычно имеет уровень обслуживания, который использует объекты домена и персистентности для реализации прецедентов. Здесь должны быть объявлены транзакции, поскольку единица работы связана с прецедентом, а не с DAO. DAO не имеет никакого способа узнать, является ли это частью более крупной транзакции. Вот почему транзакции обычно объявляются в сервисе.

+0

Спасибо за ответ. Я начал помещать прецеденты в DAO, потому что с JpaDaoSupport объекты DAO были только тонкой оболочкой, служащей для ссылки на соответствующие типы. Если я это хорошо понимаю, с другим слоем для использования, я бы закончил с другими управляемыми Spring бобами, реализующими варианты использования, AOP'ed с весенними транзакциями вместо DAO; так что это будет технически одинаково, не так ли? Какой подход вы рекомендуете позаботиться о синхронизации? Вы порекомендовали бы небольшое учебное приложение Spring 2.5 + JPA? Спасибо –

+0

Я процитировал вам весеннюю идиому - я бы рекомендовал следовать ей, особенно если это ваш первый раз с весной. Обычно я запускаю Spring на сервере приложений Java EE, таком как Tomcat или JBOSS или WebLogic, поэтому каждый запрос запускается в своем потоке. Сервер приложений останавливает запросы на обработку. Я бы рекомендовал справочные документы Spring. – duffymo

+0

Ну, что я делаю, это бэкэнд для веб-приложения, работающего на Tomcat. Весенняя ссылка хороша и обширна, но до сих пор не содержит подсказок по синхронизации - по крайней мере, я не нашел. Я предполагаю, что EntityManager # lock (объект объекта, LockModeType lockMode) - вот что я должен рассмотреть, не так ли? –

1

Как и в большинстве приложений, над которыми я работал, транзакции обычно объявляются на сервисном уровне выше DAO. Это означает, что если вы делаете несколько вставок по разным объектам, это может иметь транзакционную семантику.

Ответ зависит от вашего приложения:

  • Если вы ждете много разногласий над одной и той же сущностью, вы можете использовать pessimistic locking, который будет получить блокировку базы данных таким образом, что другой поток не может обновить более вершина. Подобно вашему синхронизированному DAO, это ограничит общую производительность приложения, и вам нужно будет позаботиться о том, чтобы избежать блокировок.
  • Если вы ожидаете, что это произойдет нечасто, используйте оптимистичную блокировку, которая будет поддерживать столбец версии и если обновление теряется исключение, которое обрабатывается в коде приложения.
3

Я думал, что когда методы DAO покрыты сделки, то только один поток будет внутри него

На основании вашего описания, каждый вызов incrementCounter должен работать в своей собственной транзакции, но потоки выполняются одновременно, поэтому одновременно выполняются две транзакции.

(т. Е. Весна позаботится о синхронизации).

Похоже, вы сбиваете транзакции с синхронизацией. Просто потому, что два потока выполняют свою собственную транзакцию, это не означает, что транзакции будут сериализованы относительно друг друга, если только уровень изоляции транзакции не будет установлен на SERIALIZABLE.

Я также думал, что менеджер субъект дал бы мне тот же экземпляр сущности из кэша контекста инерционности

Да, в пределах контекста инерционности. Если ваш контекст персистентности имеет область транзакций, то вам гарантированно получить один и тот же экземпляр объекта в рамках одной транзакции, то есть в течение одного вызова incrementCounter.

Должны ли быть синхронизированы методы DAO?

Как упоминалось в других сообщениях, обычно методы DAO являются методами CRUD, и вы обычно не синхронизируете, так как DAO просто выбирает/обновляет/etc. и не знает более широкого контекста того, что вы делаете.

Является ли весна ответственной за вызовы методов DAO, которые ведут себя транзакционно? (т. е. не допускать одновременную работу двух потоков с одним и тем же объектом)

Снова транзакция! = синхронизация.

Если нет, то как мне это достичь?

Как уже упоминалось в других сообщениях, вы можете выполнить синхронизацию на уровне Java или установить уровень изоляции транзакций на SERIALIZABLE. Другой вариант - использовать синтаксис SELECT FOR UPDATE (например, LockMode.UPGRADE в Hibernate), чтобы сообщить базу данных, которую вы намереваетесь внести, и база данных должна блокировать строки.

Если вы не привязаны к пессимистичной блокировке, вы можете реализовать оптимистичную блокировку, например. используя управление версиями Hibernate. Вы получите много оптимистических сбоев блокировки с вашим конкретным примером incrementCounter, но можно предположить, что реальное приложение не будет вести себя так.

Смежные вопросы