当初学使用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时,就已经把事务开启了。