Статьи

Миграция с Hibernate 3 на 4 с помощью интеграции с Spring

На этой неделе пришло время обновить нашу кодовую базу до последней версии Hibernate 4.x. Мы отложили нашу миграцию (все еще на Hibernate 3.3), так как более новые выпуски поддержки ветки 3.x требовали некоторых изменений API, которые, очевидно, все еще были в движении Примером является API-интерфейс UserType, который все еще отображал недостатки и должен был быть завершен в Hibernate 4. Миграция прошла довольно гладко. Адаптировать UserType к новому интерфейсу было довольно просто. Там и там были некоторые взлеты, но ничего страшного.

На что стоит обратить внимание — это интеграция Spring. Если вы ранее использовали Spring с Hibernate, вы будете использовать LocalSessionFactoryBean (или AnnotationSessionFactoryBean ) для создания SessionFactory . Для спящего 4

в отдельном пакете есть отдельный: org.springframework.orm. hibernate4 вместо org.springframework.orm. Hibernate3 . LocalSessionFactoryBean из пакета hibernate 4 подойдет как для файлов сопоставления, так и для аннотированных сущностей, поэтому вам нужен только один для обоих вариантов.

Когда обновление было завершено, все наши тесты были запущены, и приложения также работали на Tomcat с использованием локального менеджера транзакций Hibernate. Однако при работе на Glassfish с использованием транзакций JTA (и Spring JtaTransactionManager ) мы получили сообщение «Не найдено ни одного сеанса для текущего потока» при вызове sessionFactory.getCurrentSession ();

Так что, похоже, я что-то упустил в связи с конфигурацией JTA. Как вы обычно делаете с интеграцией Spring-Hibernate, вы позволяете Spring управлять транзакциями. Вы указываете диспетчер транзакций, и Spring проверяет, что все ресурсы зарегистрированы в диспетчере транзакций, и, в конечном итоге, вызывает commit или rollback. Spring интегрируется с Hibernate, поэтому он гарантирует, что сеанс сбрасывается перед фиксацией транзакции.

При использовании hibernate 3 и интеграции hibernate 3 Spring сеанс привязывается к локальному потоку. Этот метод позволяет вам использовать sessionFactory.getCurrentSession () для получения открытого сеанса в любом месте активной транзакции. Это относится как к локальному HibernateTransactionManager, так и к JtaTransactionManager . Однако с интеграцией hibernate 4 сеанс hibernate будет привязан к текущей выполняющейся транзакции JTA.

С точки зрения пользователя ничего не меняется, так как sessionFactory.getCurrentSession () все равно будет выполнять свою работу. Но при запуске JTA это означает, что Hibernate должен иметь возможность поиска менеджера транзакций, чтобы иметь возможность зарегистрировать сеанс с текущей запущенной транзакцией. Это ново, если вы переходите с Hibernate 3 на Spring, фактически вам не нужно ничего настраивать в отношении транзакций в вашей конфигурации Hibernate SessionFactory (или LocalSessionFactoryBean ). Как выяснилось, с интеграцией Hibernate 4 Spring конфигурация поиска менеджера транзакций эффективно выполняется hibernate, а не LocalSessionFactoryBean Spring. Решение было довольно простым; добавление этого в конфигурацию Hibernate ( LocalSessionFactoryBean ) решило наши проблемы:

1
2
3
<prop key="hibernate.transaction.jta.platform">
org.hibernate.service.jta.platform.internal.SunOneJtaPlatform
</prop>

SunOneJtaPlatform должен быть заменен подклассом, который отражает ваш контейнер.

См. API документы для доступных подклассов. Этот класс на самом деле говорит Hibernate, как он может искать менеджер транзакций для вашей среды. Если вы не настроите это, Hibernate ничего не сможет связать сессией с последующим вызовом исключения. Также есть свойство:

1
hibernate.current_session_context_class

Который должен указывать на org.springframework.orm.hibernate4.SpringSessionContext , но это автоматически выполняется LocalSessionFactoryBean , поэтому нет необходимости указывать его в конфигурации.

Когда это решило мою проблему «Сессия не найдена для текущего потока», была еще одна. Изменения, внесенные в базу данных внутри транзакции, не были видны после успешной фиксации транзакции. После некоторых исследований я обнаружил, что никто не вызывал session.flush () . В то время как с интеграцией hibernate 3 была зарегистрирована SpringSessionSynchronization, которая вызовет session.flush () предыдущую фиксацию транзакции (в методе beforeCommmit).

В интеграции hibernate 4 зарегистрирована SpringFlushSynchronization , которая, как следует из ее названия, также выполнит сброс. Тем не менее, это реализовано только в реальном методе сброса TransactionSynchronization , и этот метод никогда не вызывается.

Я поднял проблему для этого на багтрекере Spring, включая два примера приложения, которые ясно иллюстрируют проблему. Первый использует Hibernate 3, а другой — точно такое же приложение, но на этот раз использует hibernte 4. Второй покажет, что на самом деле информация не сохраняется в базе данных (оба приложения протестированы в соответствии с последней версией Glassfish 3.1.2). До сих пор лучший обходной путь Похоже, что создается аспект сброса, который оборачивается вокруг аннотаций @Transactional . Используя атрибут порядка, вы можете заказать транзакционную аннотацию, которая будет применена до вашего Аспекта очистки. Таким образом, ваш Аспект по-прежнему работает внутри транзакции и может сбросить сеанс. Он может получить сеанс обычным способом, внедрив SessionFactory (так или иначе), а затем вызвав sessionFactory.getCurrentSession (). Flush () .

1
2
3
4
5
6
<tx:annotation-driven order="1">
  
<bean id="flushinAspect" clas="...">
 <property name="order" value="2">
</property></bean>
</tx:annotation-driven>

или, если используется конфигурация аннотации:

1
@EnableTransactionManagement(order=1)

Обновить:

Был какой-то отзыв по этому вопросу. Оказывается, это не ошибка в интеграции Spring Hibernate, а отсутствующий элемент конфигурации Hibernate. Очевидно, для ‘hibernate.transaction.factory_class’ нужно установить JTA, по умолчанию используется JDBC, который зависит от API транзакций Hibernate для явного управления транзакциями. При установке этого параметра в JTA необходимые синхронизации регистрируются hibernate, который будет выполнять сброс. Увидеть весну
https://jira.springsource.org/browse/SPR-9404

Обновление 2:

Оказывается, после исправления конфигурации, предложенной в предыдущем выпуске, проблема все еще была. Я не собираюсь повторять все, вы можете найти подробную информацию во второй записи об ошибке, которую я представил здесь: https://jira.springsource.org/browse/SPR-9480 Это в основном сводится к тому, что в сценарии JTA с настроенным JtaTransactionFactory hibernate не обнаруживает, что он находится в транзакции, и поэтому не будет выполнять промежуточные очистки. С настроенным JtaTransactionFactory вы, как ожидается, будете управлять транзакцией через Hibernate API, а не через внешний (в нашем случае Spring) механизм. Одним из побочных эффектов является то, что в некоторых случаях вы можете читать устаревшие данные.

Пример:

1
2
3
4
5
6
//[START TX1]
Query query = session.createQuery('from Person p where p.firstName = :firstName and p.lastName = :lastName');
Person johnDoe = (Person)query.setString('firstName','john').setString('lastName','doe').uniqueResult();
johnDoe.setFirstName('Jim');
Person jimDoe = (Person)query.setString('firstName','jim').setString('lastName','doe').uniqueResult();
//[END TX1]

Случается так, что при выполнении второго запроса в строке 5 hibernate должен обнаружить, что должен сбросить предыдущее обновление, которое было сделано для присоединенного объекта в строке 4 (обновление имени с «john» на «jim»). Однако, поскольку hibernate не знает, что он выполняется внутри активной транзакции, промежуточная очистка не работает. Он будет сбрасываться только один раз до совершения транзакции Это приводит к устаревшим данным, так как 2-й запрос не найдет ‘jim’ и вернет ноль вместо этого Решение (см. Ответ Юргена Хеллера в этом выпуске) вместо этого настраивает hibernate.transaction.factory_class для org.hibernate.transaction.CMTTransactionFactory . Сначала я немного скептически относился к CMT, как к контейнерам EJB. Однако, если вы читаете документацию по Java на CMTTransaction, это имеет смысл:

01
02
03
04
05
06
07
08
09
10
/**
 * Implements a transaction strategy for Container Managed Transaction (CMT) scenarios.  All work is done in
 * the context of the container managed transaction.
 *
 * The term 'CMT' is potentially misleading; the pertinent point simply being that the transactions are being
 * managed by something other than the Hibernate transaction mechanism.
 *
 * Additionally, this strategy does *not* attempt to access or use the {@link javax.transaction.UserTransaction} since
 * in the actual case CMT access to the {@link javax.transaction.UserTransaction} is explicitly disallowed.  Instead
 * we use the JTA {@link javax.transaction.Transaction} object obtained from the {@link TransactionManager}

После этого все, кажется, работает нормально. Итак, в заключение, если вы хотите, чтобы hibernate управлял транзакцией JTA через UserTransaction, вы должны использовать JtaTransactionFactory. В этом случае вы должны использовать Hibernate API для управления транзакцией. Если кто-то еще управляет транзакцией (Spring, EJB-контейнер …), вы должны использовать CMTTransactionFactory. После этого Hibernate вернется к регистрации синхронизаций, проверив наличие активных javax.transaction.Transaction с помощью javax.transaction.TransactionManager. Если возникнет какая-либо другая проблема, я обновлю эту запись соответственно.

Ссылка: переход с Hibernate 3 на 4 с помощью интеграции с Spring от нашего партнера по JCG