天天看点

spring + hibernate时,current_session_context_class配置问题

当初学使用spring来配置hibernate的事务管理时,当要把current session绑定到线程上,本人很习惯式的把hibernate的current_session_context_class的值设置为thread(hibenate native api 提供三个值:thread,jta, manage).

可是当调用session.save()方法是遇到以下的错:save is not valid without transaction.

applicationContext.xml

<!-- ORM的sessionFactory -->

    <bean id="sessionFactory"

        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

        <!-- Set the locations of multiple Hibernate XML config files, for example

            as classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml". -->

        <property name="configLocation" value="classpath:hibernate.cfg.xml" />

        <property name="configurationClass" value="org.hibernate.cfg.Configuration" />

    </bean>

    <tx:annotation-driven/>

    <!-- 配置事务处理意见 -->

    <tx:advice id="baseAdvice" transaction-manager="txManager">

        <!-- 事务的属性 -->

        <tx:attributes>

            <tx:method name="list*" propagation="REQUIRED" read-only="true"/>

            <tx:method name="*"/>

        </tx:attributes>

    </tx:advice>

    <!-- 配置事务的aop -->

    <aop:config>

        <aop:pointcut id="daoPointcut" expression="execution(* com.mcao.dao.*.*(..))" />

        <aop:advisor advice-ref="baseAdvice" pointcut-ref="daoPointcut" />

    </aop:config>

    <!-- 定义事务管理器 -->

    <bean id="txManager"

        class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <!-- 设置关联的sessionFactory -->

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <bean id="userDao" class="com.mcao.dao.UserDaoImpl">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <bean id="userService" class="com.mcao.service.UserService">

        <property name="userDao" ref="userDao" />

    </bean>

hibernate.cfg.xml

<session-factory>

        <!-- mysql jdbc 连接配置 -->

        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>

        <property name="connection.url">jdbc:mysql://localhost:3306/test</property>

        <property name="connection.username">root</property>

        <property name="connection.password">123456</property>

        <!-- JDBC connection pool (use the built-in) -->

        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->

        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

        <!-- Enable Hibernate's automatic session context management -->

        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache -->

        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->

        <property name="show_sql">true</property>

        <!-- batch size -->

        <property name="hibernate.jdbc.batch_size">40</property>

        <!-- Drop and re-create the database schema on startup -->

        <property name="hbm2ddl.auto">update</property>

        <mapping class="com.mcao.domain.User"/>

    </session-factory>

userDaoImpl:

public void save(Serializable domainObject) {

        Session session = sessionFactory.getCurrentSession();

        session.save(domainObject); // 错误发生在此行

    }

在网上查了半天,也没查到真正的原因。最后看了源代码才明白。

当current_session_context_class = thread时,具体的类就是org.hibernate.context.ThreadLocalSessionContext.

Session session = sessionFactory.getCurrentSession();只是获取当前的session,并未开启transaction(hibernate native的事务开始,是需要调用session.beginTransaction())当执行session.save(domainObject); 当前的session会被TransactionProtectionWrapper包装(代理模式),因此session.save(domainObject);被执行时,实际是通过TransactionProtectionWrapper.invoke代理执行。

这个代理添加了一些对于session调用的方法的约束,除了以下列举的方法,其他的都需要在事务中执行。由于事务未开启,所有realSession.getTransaction().isActive()为false,当然就会抛出异常了。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            try {

                // If close() is called, guarantee unbind()

                if ( "close".equals( method.getName()) ) {

                    unbind( realSession.getSessionFactory() );

                }

                else if ( "toString".equals( method.getName() )

                         || "equals".equals( method.getName() )

                         || "hashCode".equals( method.getName() )

                         || "getStatistics".equals( method.getName() )

                         || "isOpen".equals( method.getName() )

                         || "getListeners".equals( method.getName() ) //useful for HSearch in particular

                        ) {

                    // allow these to go through the the real session no matter what

                }

                else if ( !realSession.isOpen() ) {

                    // essentially, if the real session is closed allow any

                    // method call to pass through since the real session

                    // will complain by throwing an appropriate exception;

                    // NOTE that allowing close() above has the same basic effect,

                    //   but we capture that there simply to doAfterTransactionCompletion the unbind...

                }

                else if ( !realSession.getTransaction().isActive() ) {

                    // limit the methods available if no transaction is active

                    if ( "beginTransaction".equals( method.getName() )

                         || "getTransaction".equals( method.getName() )

                         || "isTransactionInProgress".equals( method.getName() )

                         || "setFlushMode".equals( method.getName() )

                         || "getSessionFactory".equals( method.getName() ) ) {

                        log.trace( "allowing method [" + method.getName() + "] in non-transacted context" );

                    }

                    else if ( "reconnect".equals( method.getName() )

                              || "disconnect".equals( method.getName() ) ) {

                        // allow these (deprecated) methods to pass through

                    }

                    else {

                        throw new HibernateException( method.getName() + " is not valid without active transaction" );

                    }

                }

                log.trace( "allowing proxied method [" + method.getName() + "] to proceed to real session" );

                return method.invoke( realSession, args );

            }

            catch ( InvocationTargetException e ) {

                if ( e.getTargetException() instanceof RuntimeException ) {

                    throw ( RuntimeException ) e.getTargetException();

                }

                else {

                    throw e;

                }

            }

        }

解决方法有二:

1, 添加session.beginTransaction(), 开启事务。

2, current_session_context_class对应的class设置为org.springframework.orm.hibernate3.SpringSessionContext

或者不指定(同样是org.springframework.orm.hibernate3.SpringSessionContext),在此类中,获取current session时,就已经把事务开启了。