Несколько потоков 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);
Другие вопросы по тегам