Защо моят (пролетен) HibernateTransactionManager не работи в wicket?

Опитах се да съкратя това до това, което смятам за уместно, надявам се да е достатъчно и да не е непосилно. Моля помогнете!

Преобразувам малко уеб приложение 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(), получавам изключение за хибернация, защото никой не е извикал 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 и моето внедряване също се оказа проблематично.

Ето моето приложениеContext.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, ако имам избор.

Редактиране:

Ревизирах приложения Context по-горе, за да премахна конфигурацията на 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, и да използва локален контекст на нишка на негово място. Премахването на това свойство реши проблема (hibernate използва 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 прави окабеляването, hibernate не трябва да знае нищо за spring, това е целият смисъл на използването на spring (за отделяне на отделните слоеве). Служенето и дао е разделяне, което не всеки използва. Важната част е, че публичните методи, поддържани от интерфейса, са маркирани като транзакционни, тъй като всички методи, маркирани като транзакционни, ще бъдат прихванати от прокси, което извършва обработката на транзакциите, докато други няма.

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

person Sean Patrick Floyd    schedule 22.09.2010
comment
Премахнах конфигурацията на AOP (което не направи нищо, не работи); иначе това, което си публикувал, е същото като това, което вече имам (доколкото виждам). Все още имам същия проблем. Нямам отделни класове за услуга и DAO. Това важно ли е за проблема? - person RMorrisey; 22.09.2010
comment
Използвам wicket, който не е EJB контейнер, така че си помислих, че може би трябва да направя малко допълнителна работа, използвайки AOP нещата, за да накарам @Transactional да работи правилно? Самата хибернация трябва ли да знае по някакъв начин за мениджъра на транзакциите? - person RMorrisey; 22.09.2010
comment
Използвам пружина и wicket също, в tomcat, без ejb контейнер. За повечето цели Spring предоставя всичко, от което се нуждаете от EJB контейнер. Освен това вижте моя актуализиран отговор - person Sean Patrick Floyd; 22.09.2010
comment
Изглежда, че sessionFactory.getCurrentSession() връща различна сесия в DAO от тази, която TransactionInterceptor се опитва да управлява? - person RMorrisey; 22.09.2010
comment
Това, което не разбирам, е защо използвате @SpringBean във вашето dao. @SpringBean е специфично за wicket неща. Вашият dao/service слой не трябва да знае нищо за wicket... - 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 се инжектира в wicket компонента с помощта на инстанционен слушател (който извиква InjectorHolder.getInjector().inject(component) след като компонентът е конструиран). Конфигурацията на DAO bean в моя applicationContext.xml има: ‹ref bean=sessionFactory /›, така че sessionFactory bean се инжектира в DAO. Ако това не е правилният модел, може би това е проблемът? Какъв е правилният, нормален начин за получаване на препратка към SessionFactory? Видях в програмата за отстраняване на грешки, че SessionFactory, която е инжектирана, е същият екземпляр, който TransactionInterceptor използва - person RMorrisey; 22.09.2010
comment
Гласувах с +1 за вашата помощ, въпреки че все още не съм намерил решение - person RMorrisey; 23.09.2010