Почему мой (весенний) HibernateTransactionManager не работает в калитке?

Я попытался сократить это до того, что я считаю уместным, я надеюсь, что этого достаточно и не ошеломляюще. Пожалуйста помоги!

Я конвертирую небольшое веб-приложение wicket+databinder+hibernate для использования wicket+spring+hibernate. У меня есть класс обслуживания DAO с спящим SessionFactory, введенным Spring. Я могу выполнять операции только для чтения, используя фабрику сеансов (автоматическая фиксация включена по умолчанию). Я хочу использовать HibernateTransactionManager и аннотацию @Transactional для выполнения транзакционной операции.

Я определяю реализацию службы DAO, которая использует внедренную SessionFactory в методе с пометкой @Transactional:

public class DAO implements IDAO {
 @SpringBean
 private SessionFactory sessionFactory;

 public DAO() {
  super();
 }

 @Transactional
 public Object execute(SessionUnit sessionUnit) {
  Session sess = sessionFactory.getCurrentSession();
  Object result;
  result = sessionUnit.run(sess);
  sess.flush();
  return result;
 }

 public void setSessionFactory(SessionFactory sessionFactory) {
  this.sessionFactory = sessionFactory;
 }

 @Transactional
 public boolean isObjectPersistent(Object object) {
  return sessionFactory.getCurrentSession().contains(object);
 }
}

Когда я пытаюсь вызвать isObjectPersistent(), я получаю исключение hibernate, потому что никто не вызывал session.beginTransaction():

Caused by: org.hibernate.HibernateException: contains is not valid without active transaction
 at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:338)
 at $Proxy38.contains(Unknown Source)
 at com.gorkwobbler.shadowrun.karma.db.hibernate.DAO.isObjectPersistent(DAO.java:35)
(reflection stuff omitted...)
 at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
 at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
 at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
(reflection stuff omitted...)
 at org.apache.wicket.proxy.LazyInitProxyFactory$JdkHandler.invoke(LazyInitProxyFactory.java:416)
 at org.apache.wicket.proxy.$Proxy36.isObjectPersistent(Unknown Source)

Я также заметил из полной трассировки стека, что вызывается OpenSessionInViewFilter, я не уверен, что это имеет значение. Дайте мне знать, если вам нужна остальная часть трассировки стека.

Если я создам собственный подкласс WebRequestCycle, который начинает транзакцию, я смогу обойти это. Мне кажется, что это подрывает цель @Transactional, и моя реализация также оказалась проблематичной.

Вот мой applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Reference: http://wicketinaction.com/2009/06/wicketspringhibernate-configuration/ -->
<beans default-autowire="autodetect"
    xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <!-- bean definitions -->
    <bean id="wicketApplication" class="com.gorkwobbler.shadowrun.karma.view.wicket.core.WicketApplication" />

    <bean id="placeholderConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="ignoreUnresolvablePlaceholders" value="false" />
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
        <property name="ignoreResourceNotFound" value="false" />
        <property name="locations">
            <list>
                <value>classpath*:/application.properties</value>
            </list>
        </property>
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>${jdbc.driver}</value>
        </property>
        <property name="url">
            <value>${jdbc.url}</value>
        </property>
        <property name="username">
            <value>${jdbc.username}</value>
        </property>
        <property name="password">
            <value>${jdbc.password}</value>
        </property>
    </bean>

    <tx:annotation-driven transaction-manager="txManager" />

    <!-- setup transaction manager  -->
    <bean id="txManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>

    <!-- hibernate session factory -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="configLocation">
            <value>classpath:/hibernate.cfg.xml</value>
        </property>
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
            </props>
        </property>
        <property name="packagesToScan">
            <list>
                <value>com.gorkwobbler.shadowrun.karma.domain</value>
                <value>com.gorkwobbler.shadowrun.karma.domain.*</value>
            </list>
        </property>
    </bean>

    <bean id="dao"
        class="com.gorkwobbler.shadowrun.karma.db.hibernate.DAO">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>

    <!-- Don't know what this is for, but it was in the sample config I started from --> 
    <!-- <context:component-scan base-package="com.gorkwobbler.shadowrun.karma" />  -->
</beans>

Как я могу заставить свой DAO начать транзакцию, зафиксировать в конце этого метода или выполнить откат в случае ошибки? Я хочу использовать максимально минимальную/стандартную конфигурацию; Я предпочитаю аннотации XML, если есть выбор.

Изменить:

Я пересмотрел applicationContext выше, чтобы удалить элементы конфигурации AOP, которые все равно не работали.

Используя отладчик, я определил, что SessionImpl, хранящийся в карте держателя сеанса TransactionInterceptor, не является тем же сеансом, что и SessionImpl, который извлекается в методе DAO при вызове sessionFactory.getCurrentSession(). Кто-нибудь может объяснить, почему это так? Что я делаю неправильно? Магия не работает. знак равно

Изменить

Я также заметил следующее сообщение в моей консоли во время запуска:

WARN  - stractEhcacheRegionFactory - No TransactionManagerLookup found in Hibernate config, XA Caches will be participating in the two-phase commit!

person RMorrisey    schedule 22.09.2010    source источник


Ответы (2)


Оказывается, проблема на самом деле была не в информации о конфигурации, которую я разместил. Прости!

Моя приведенная выше конфигурация ссылается на внешний файл hibernate.cfg.xml, в котором объявлено следующее свойство:

    <!-- Enable Hibernate's automatic session context management -->
    <property name="current_session_context_class">thread</property>

Должно быть, я скопировал это из какого-то примера файла конфигурации гибернации. Это свойство заставило мою SessionFactory игнорировать контекст сеанса, предоставленный Spring, и использовать вместо него локальный контекст потока. Удаление этого свойства устранило проблему (спящий режим по умолчанию использует контекст JTA, если он не указан).

person RMorrisey    schedule 23.09.2010
comment
У меня та же проблема, однако мой текущий класс контекста сеанса имеет значение: ‹property name=current_session_context_class›org.hibernate.context.ThreadLocalSessionContext‹/property› удаление его вызывает исключение - person Heetola; 15.06.2012

Это можно сделать намного проще. Прочитайте этот раздел 13.3.3 (Hibernate) Декларативное разграничение транзакций, особенно последняя часть. Этой конфигурации обычно достаточно, если вы используете @Transactional:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <!-- SessionFactory, DataSource, etc. omitted -->

  <bean id="transactionManager"
            class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>

  <tx:annotation-driven/>

  <bean id="myProductService" class="product.SimpleProductService">
    <property name="productDao" ref="myProductDao"/>
  </bean>

</beans>

Чтобы ответить на ваши комментарии:

Нет, если вы вводите компоненты через Spring, Spring выполняет проводку, спящему режиму не нужно ничего знать о Spring, в этом весь смысл использования Spring (для разделения отдельных слоев). Служение и дао — это разделение, которым пользуются не все. Важная часть заключается в том, что общедоступные методы, поддерживаемые интерфейсом, помечены как транзакционные, потому что все методы, помеченные как транзакционные, будут перехвачены прокси-сервером, который выполняет обработку транзакций, а другие — нет.

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

person Sean Patrick Floyd    schedule 22.09.2010
comment
Я удалил конфигурационный материал AOP (это ничего не дало, это не сработало); в противном случае то, что вы опубликовали, совпадает с тем, что у меня уже есть (насколько я вижу). У меня все еще та же проблема. У меня нет отдельных классов для сервиса и DAO. Имеет ли это значение для проблемы? - person RMorrisey; 22.09.2010
comment
Я использую wicket, который не является контейнером EJB, поэтому я подумал, что, возможно, мне придется проделать дополнительную работу, используя АОП, чтобы заставить @Transactional работать правильно? Нужно ли самому спящему режиму как-то знать о диспетчере транзакций? - person RMorrisey; 22.09.2010
comment
Я также использую spring и wicket в tomcat без контейнера ejb. Для большинства целей Spring предоставляет все, что вам нужно, от контейнера EJB. Также смотрите мой обновленный ответ - person Sean Patrick Floyd; 22.09.2010
comment
Похоже, что sessionFactory.getCurrentSession() возвращает другой сеанс в DAO, чем тот, которым пытается управлять TransactionInterceptor? - person RMorrisey; 22.09.2010
comment
Чего я не понимаю, так это почему вы используете @SpringBean в своем дао. @SpringBean - это вещи, специфичные для калитки. Ваш уровень dao/service не должен ничего знать о калитке... - person Sean Patrick Floyd; 22.09.2010
comment
Кроме того, если по какой-то причине вы хотите использовать @SpringBean в чем-то, что не является компонентом Wicket, инъекция не произойдет без явного InjectorHolder.getInjector().inject(this) в конструкторе. - person Don Roby; 22.09.2010
comment
Да, но, видимо, инъекция действительно произошла, иначе он получил бы NPE. - person Sean Patrick Floyd; 22.09.2010
comment
Инъекция действительно происходит. Сам DAO внедряется в компонент калитки с помощью прослушивателя экземпляров (который вызывает InjectorHolder.getInjector().inject(component) после создания компонента). Конфигурация компонента DAO в моем applicationContext.xml имеет: ‹ref bean=sessionFactory /›, поэтому компонент sessionFactory внедряется в DAO. Если это не правильный шаблон, может быть, это проблема? Каков правильный, нормальный способ получить ссылку на SessionFactory? В отладчике я увидел, что внедряемая SessionFactory — это тот же экземпляр, который использует TransactionInterceptor. - person RMorrisey; 22.09.2010
comment
Проголосовал +1 за вашу помощь, хотя я до сих пор не нашел решения - person RMorrisey; 23.09.2010