Несколько потоков hibernate предотвращают несколько save(), JTA необходимо?
Я использую сеанс гибернации для каждой модели запроса для своего веб-приложения. Моя транзакция jdbc начинается в начале каждого веб-запроса и фиксируется в конце.
// Неуправляемая среда
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
Я столкнулся с проблемой, когда я проверяю существование сущности (A) на основе нескольких параметров и выполняю вставку, только если она не существует.
public synchronized myMethod(param1, param2) {
MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
if (entity == null) {
entity = .../create entity
MyEntityADAO.save(entity);
}
}
проблема в том, что синхронизация не помогает, потому что вызов MyEntityADAO.save() фактически не записывает в базу данных, когда текущий запущенный поток выходит из метода и снимает блокировку, запись в базу данных происходит после фиксации транзакции, которая как правило, что мне нужно для моего приложения, за исключением нескольких сценариев. Приведенный выше код вызывает несколько записей, сохраненных с одинаковыми параметрами в многопоточной среде.
Я попытался выполнить код сохранения в своей новой сессии и транзакции:
public synchronized myMethod(param1, param2) {
MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
if (entity == null) {
entity = .../create entity
Session session = HibernateUtil.createSession();
MyEntityADAO.save(entity);
Transaction t = session.beginTransaction();
}
}
вышеуказанное вызывает проблемы с 2 открытыми сессиями, загружающими одну и ту же коллекцию с помощью hibernate в некоторых случаях.
Должен ли я заключать каждый вызов DAO в свою собственную транзакцию и использовать распространение транзакций с JTA? Есть ли способ избежать JTA? Можно ли зафиксировать транзакцию, связанную с основным сеансом, после вызова MyEntityADAO.save() и вызвать beginTransaction в основном сеансе сразу после завершения транзакции, а транзакция зафиксирована в конце запроса, как сейчас?
2 ответа
Решение, которое работало для меня и моих конкретных требований к приложениям, пытаясь избежать JTA и вложенных транзакций:
Использование ManagedSessionContext, потому что org.hibernate.context.ThreadLocalSessionContext закроется и создаст новый сеанс для каждой транзакции. Вы столкнетесь с проблемами с объектами, с которыми связаны коллекции, если вы загрузите эти объекты в нескольких открытых сеансах (когда вы создадите несколько транзакций для одного запроса).
- Я открываю сеанс гибернации и привязываю его к контексту в начале моего веб-запроса.
- Любой метод уровня обслуживания, который нуждается в проверке существования перед вставкой, помечается как синхронизированный, глобальная транзакция фиксируется с помощью оператора вставки и запускается новая транзакция.
В конце запроса транзакция, привязанная к сеансу, фиксируется
публичный синхронизированный myMethod (param1, param2) {
MyEntityA entity = MyEntityADAO.findEntity(param1, param2) if (entity == null) { entity = .../create entity MyEntityADAO.save(entity); HibernateUtil.getCurrentSession().getTransaction().commit(); HibernateUtil.getCurrentSession().getTransaction().begin(); }
}
Я знаю, что это уродливо и не будет работать для всех в каждом сценарии, но после очень интенсивного поиска по управлению транзакциями, уровням изоляции, блокировкам, версиям - это единственное найденное мной решение, которое сработало для меня. Я не использую Spring, и я не использую контейнер Java EE, используя Tomcat 6.
Согласованность данных в базе данных не должна быть нарушена путем внесения лишь некоторой части атомарного изменения в свою собственную транзакцию. И хотя некоторая синхронизация может работать в вашей среде, если вам нужно кластеризовать ваше приложение или если несколько приложений обращаются к базе данных, это не решит проблему.
Что вы должны сделать, это поставить уникальное ограничение в базе данных на [param1 - param2]
, Это приведет к откату одной из двух транзакций в случае возникновения гонки.
Если вы решите по-прежнему изолировать код проверки / вставки в своей собственной транзакции (потому что это не проблема, если это удастся, а внешняя транзакция завершится неудачей), я не вижу, как JTA будет проблемой. Предположим, что вы используете EJB или Spring, просто поместите этот метод в его собственный EJB/bean-компонент и отметьте метод как транзакционный с распространением REQUIRES_NEW.
Таким образом, код будет выглядеть так:
// some code
Long id = myBean.checkIfExistOrCreate(param1, param2); // this methos call starts a new transaction
// now we're sure that the entity exists. Load it in the current session.
MyEntity e = em.find(MyEntity.class, id);
Если вы не можете синхронизировать checkIfExistOrCreate
, затем попробуйте вызвать его, перехватить все исключения, которые он может выдать, и повторить вызов:
Long id = null;
try {
id = myBean.checkIfExistOrCreate(param1, param2);
}
catch (Exception e) { // a well-defined exception would be better
// the transaction roled back: retry
id = myBean.checkIfExistOrCreate(param1, param2);
}
// now we're sure that the entity exists. Load it in the current session.
MyEntity e = em.find(MyEntity.class, id);