views:

118

answers:

2

I tried to shorten this to what I think is relevant, I hope it's sufficient and not overwhelming. Please help!

I'm converting a small wicket+databinder+hibernate web application to use wicket+spring+hibernate. I have a DAO service class, with a hibernate SessionFactory injected by Spring. I am able to do read-only operations using the session factory (autocommit is on by default). What I want to do is use the HibernateTransactionManager and the @Transactional annotation to do a transactional operation.

I define a DAO service implementation, which uses an injected SessionFactory in a method marked @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);
 }
}

When I try to call isObjectPersistent(), I get a hibernate exception because no one has called 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)

I also notice from the full stack trace that the OpenSessionInViewFilter is being invoked, I'm not sure if that's relevant. Let me know if you need the rest of the stack trace.

If I create a custom WebRequestCycle subclass, which begins a transaction, I can get past this. This seems to me to undermine the purpose of @Transactional, and my implementation of it also turned out to be problematic.

Here is my 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"&gt;

    <!-- 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>

How can I get my DAO to begin a transaction, commit at the end of that method, or rollback on error? I want to use the most minimal/standard configuration possible; I prefer annotations over XML if given the choice.

Edit:

I revised the applicationContext above to remove the AOP configuration stuff, which wasn't working, anyway.

Using the debugger, I determined that the SessionImpl stored in the TransactionInterceptor's session holder map is not the same session as the SessionImpl that is retrieved in the DAO method when I call sessionFactory.getCurrentSession(). Can anyone explain why this is? What am I doing wrong? The magic is not working. =(

Edit

I also notice the following message in my console during startup:

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

This can be made much simpler. Read this section 13.3.3 (Hibernate) Declarative transaction demarcation, especially the last part. This is usually enough configuration if you use @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"&gt;

  <!-- 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>

To answer your comments:

No, if you inject the components via Spring, Spring does the wiring, hibernate doesn't need to know anything about spring, that's the whole point of using spring (to decouple the individual layers). Service and dao is a separation that not everybody uses. The important part is that the public methods backed by the interface are marked as transactional, because all methods marked as transactional will be intercepted by a proxy that does the transaction handling, while others won't.

You may want to read the general section about Declarative Transaction Managmenet in Spring to understand the process (this applies to all transactional technologies, not just to hibernate).

seanizer
I removed the AOP configuration stuff (which did nothing, it didn't work); otherwise, what you posted is the same as what I already have (as far as I can see). I still have the same issue. I do not have separate classes for service and DAO. Is this significant to the problem?
RMorrisey
I am using wicket, which is not an EJB container, so I thought that maybe I had to do some extra legwork using the AOP stuff to make the @Transactional work properly? Does hibernate itself need to know about the transaction manager somehow?
RMorrisey
I use spring and wicket also, in tomcat, without an ejb container. For most purposes, Spring provides everything you'd need from an EJB container. Also, see my updated answer
seanizer
It seems like sessionFactory.getCurrentSession() returns a different session in the DAO then the one that the TransactionInterceptor is trying to manage?
RMorrisey
What I don't get is why you are using @SpringBean in your dao. @SpringBean is wicket-specific stuff. Your dao/service layer should not know anything about wicket...
seanizer
Also if for some reason you do wish to use @SpringBean in something that's not a Wicket component, the injection won't happen without an explicit InjectorHolder.getInjector().inject(this) in the constructor.
Don Roby
Yes, but apparently the injection does happen, or he'd be getting a NPE.
seanizer
The injection does happen. The DAO itself is injected into the wicket component using an instantation listener (which calls InjectorHolder.getInjector().inject(component) after the component is constructed). The DAO bean config in my applicationContext.xml has: <ref bean="sessionFactory" /> so that the sessionFactory bean is injected into the DAO. If this is not the right pattern, maybe this is the problem? What is the correct, normal way to get a reference to the SessionFactory? I saw in the debugger that the SessionFactory which is injected is the same instance TransactionInterceptor uses
RMorrisey
Voted +1 for your help although I still have not figured out a solution
RMorrisey
A: 

It turns out that the problem was not actually in the configuration information that I posted. Sorry!

My above configuration links to an externalized hibernate.cfg.xml, which declared the following property:

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

I must've copied this from some sample hibernate config file somewhere. This property caused my SessionFactory to ignore the session context provided by spring, and use a thread-local context in its place. Removing this property fixed the problem (hibernate uses the JTA context by default if none is specified).

RMorrisey