由OpenSessionInViewFilter报错引出的spring3.0事务配置问题及解决办法

秦怀卓 2011-05-18

开发环境

IDE:eclipse3.4

FrameWork:spring3.0+springmvc3.0+hibernate3.2

Server:Tomcat6.0

使用OpenSessionInViewFilter的原因

引用Hibernate允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个HibernateSession范围之内进行。如果Service层返回一个启用了延迟加载功能的领域对象给Web层,当Web层访问到那些需要延迟加载的数据时,由于加载领域对象的HibernateSession已经关闭,这些导致延迟加载数据的访问异常。而Spring为我们提供的OpenSessionInViewFilter过滤器为我们很好的解决了这个问题。OpenSessionInViewFilter的主要功能是使每个请求过程绑定一个HibernateSession,即使最初的事务已经完成了,也可以在Web层进行延迟加载的操作。OpenSessionInViewFilter过滤器将HibernateSession绑定到请求线程中,它将自动被Spring的事务管理器探测到。

在Service层执行插入,删除及数据更新操作时会包以下错误

引用org.springframework.dao.InvalidDataAccessApiUsageException:Writeoperationsarenotallowedinread-onlymode(FlushMode.MANUAL):TurnyourSessionintoFlushMode.COMMIT/AUTOorremove'readOnly'markerfromtransactiondefinition

报错原因:

看过OpenSessionInViewFilter以后就知道,OpenSessionInViewFilter的getSession方法中会对session的flushMode设定一个默认为NEVER的值

解决办法有两个:

1:继承OpenSessionInViewFilter,并覆盖getSession,设施flushMode为AUTO

2:通过spring的事务管理Hibernate的sessionFactory,对不同的数据操作作事务的隔离及限制

因为反正都要用到spring作事务管理,所以我在项目中采用了第二种方法

spring2.5以后就支持annotation配置了,到了3.0配置更加简化方便了,但对于新手来说,如果不懂具体的spring运作原理,很容易犯错误,并且在报错之后很难发现错误原因。

在此特将spring的配置方法写出来,并经过实例测试,是可以通过并正常运行的。希望对在这方面遇到问题的朋友有帮助,spring的基于annotation请不明白的朋友在网上查看相关文档。

主要配置文件有两个

app-config.xml配置数据源,hibernate,事务等

mvc-config.xml配置springmvc的controller,viewResolver,messageConvert等

本文主要涉及app-config.xml的配置

首先通过以下配置,自动扫描com包内作了annotation注解的类,并实例化(至于里面为什么会有个exclude-filter请看文章的后面)

<context:component-scanbase-package="com">

<context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>

</context:component-scan>

hibernate及数据源的配置

<beanid="sessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSource"/>

<propertyname="packagesToScan"value="com"/>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>

<!--<propkey="hibernate.dialect">org.hibernate.dialect.sql</prop>-->

<!--<propkey="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>-->

<propkey="hibernate.show_sql">true</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>

<propkey="hibernate.cache.use_query_cache">true</prop>

<propkey="hibernate.hbm2ddl.auto">update</prop>

<propkey="hibernate.format_sql">true</prop>

<propkey="hibernate.use_sql_comments">true</prop>

<!--

<propkey="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</prop>

<propkey="hibernate.search.default.indexBase">/WEB-INF/indexes</prop>-->

</props>

</property>

</bean>

配置transactionManager管理事务

<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"autowire="byName"/>

接下来是自动化事务

<tx:annotation-driven/>

如果前面配置的事务id不是"transactionManager",比如id="txManager",则应该如下配置

<tx:annotation-driventransaction-manager="txManager"/>

这里需要注意的问题,看过spring的参考手册的朋友都会这样配置

<context:component-scanbase-package="com">

因为省事,一句话可以自动把com包低下的所有带annotation注解的类都实例化并配好了,但如果这样简单的配置会导致刚才spring的事务配置失效

原因:

实例化@Controller类时,Spring会自动把关联的@Service(此@Service已做了@Transaction事务注解)类实例化,此时事务并未生效,导致@Transaction注解无效,事务未被注册

因此需要把@Controller和其它的@Service,@Components,@Reposity等分开实例化,在事务生效后,并且其它组件都实例化完成后,@Controller最后实例化,app-config.xml和mvc-config.xml的配置分别如下

app-config.xml

<!--扫描com及子包,自动实例化带@注释的实例,这里排除@Controller,所有controller的实例化在mvc-config中完成-->

<context:component-scanbase-package="com">

<context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>

</context:component-scan>

mvc-config.xml(注意里面的注释)

<!--扫描com及子包,自动实例化带@controller注释的实例,

由于实例化controller时会对controller关联的Service类一同实例化,所以这里需要排除@Service

-->

<context:component-scanbase-package="com">

<context:include-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>

<context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Service"/>

</context:component-scan>

此时在带@Service注解的类中添加@Transactional标签就会其作用了,例如

@Service("articleService")

@Transactional

publicclassArticleServiceextendsGenericService<Article,Long>{

@Autowired

privateArticleDaoarticleDao;

@Transactional(readOnly=true)

publicList<Article>getArticles(){

/*...*/

}

@Transactional(propagation=Propagation.REQUIRED)

publicvoidsaveArticle(longid){

/*..*/

}

更多事务注解的方式请参考spring3.0reference

这这样需要对每个Service的方法都要做类似的注解,多了会很麻烦,也难管理维护。

下面通过spring的aop对事务进行自动化管理,配置如下

<!--配置aop切入点和事务访问策略-->

<aop:config>

<aop:pointcutid="serviceOperation"expression="execution(*com.*.service..*Service.*(..))"/>

<aop:advisorpointcut-ref="serviceOperation"advice-ref="txAdvice"/>

</aop:config>

<tx:adviceid="txAdvice">

<tx:attributes>

<tx:methodname="del*"propagation="REQUIRED"/>

<tx:methodname="save*"propagation="REQUIRED"/>

<tx:methodname="update*"propagation="REQUIRED"/>

<tx:methodname="add*"propagation="REQUIRED"/>

<tx:methodname="create*"propagation="REQUIRED"/>

<tx:methodname="get*"read-only="true"/>

<tx:methodname="*"/>

</tx:attributes>

</tx:advice>

注意

1这部分代码要放在<tx:annotation-driven/>的上面,否则tx:advice不起作用

2<tx:adviceid="txAdvice">如果前面配置的事务id不是"transactionManager",比如id="txManager",则应该修改为<tx:adviceid="txAdvice"transaction-manager="txManager">

app-config.xml的整个配置如下

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:aop="http://www.springframework.org/schema/aop"

xsi:schemaLocation="

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-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">

<!--扫描com及子包,自动实例化带@注释的实例,这里排除@Controller,所有controller的实例化在mvc-config中完成-->

<context:component-scanbase-package="com">

<context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>

</context:component-scan>

<beanid="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<propertyname="locations">

<list>

<value>/WEB-INF/prop/database.properties</value>

</list>

</property>

</bean>

<!--JNDIDataSourceforJ2EEenvironmentsuserc3p0-->

<beanid="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close">

<propertyname="driverClass"><value>${jdbc.driverClass}</value></property>

<propertyname="jdbcUrl"><value>${jdbc.jdbcUrl}</value></property>

<propertyname="user"><value>${jdbc.user}</value></property>

<propertyname="password"><value>${jdbc.password}</value></property>

<propertyname="minPoolSize"><value>${jdbc.minPoolSize}</value></property>

<propertyname="maxPoolSize"><value>${jdbc.maxPoolSize}</value></property>

<propertyname="maxIdleTime"><value>${jdbc.maxIdleTime}</value></property>

</bean>

<!--hibernate.hbm2ddl.auto参数说明

validate加载hibernate时,验证创建数据库表结构

create每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。

create-drop加载hibernate时创建,退出是删除表结构

update加载hibernate自动更新数据库结构

none不更新数据库结构

-->

<beanid="sessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSource"/>

<propertyname="packagesToScan"value="com"/>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>

<!--<propkey="hibernate.dialect">org.hibernate.dialect.sql</prop>-->

<!--<propkey="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>-->

<propkey="hibernate.show_sql">true</prop>

<propkey="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>

<propkey="hibernate.cache.use_query_cache">true</prop>

<propkey="hibernate.hbm2ddl.auto">update</prop>

<propkey="hibernate.format_sql">true</prop>

<propkey="hibernate.use_sql_comments">true</prop>

<!--

<propkey="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</prop>

<propkey="hibernate.search.default.indexBase">/WEB-INF/indexes</prop>-->

</props>

</property>

</bean>

<!--配置hibernate事务-->

<beanid="transactionManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"autowire="byName"/>

<!--配置aop切入点和事务访问策略-->

<aop:config>

<aop:pointcutid="serviceOperation"expression="execution(*com.*.service..*Service.*(..))"/>

<aop:advisorpointcut-ref="serviceOperation"advice-ref="txAdvice"/>

</aop:config>

<tx:adviceid="txAdvice">

<tx:attributes>

<tx:methodname="del*"propagation="REQUIRED"/>

<tx:methodname="save*"propagation="REQUIRED"/>

<tx:methodname="update*"propagation="REQUIRED"/>

<tx:methodname="add*"propagation="REQUIRED"/>

<tx:methodname="create*"propagation="REQUIRED"/>

<tx:methodname="get*"read-only="true"/>

<tx:methodname="*"/>

</tx:attributes>

</tx:advice>

<!--tx:annotation自动配置事务,注意这个标签必需放在tx:advice下面,否则不起作用-->

<tx:annotation-driven/>

<!--ApplicationMessageBundle-->

<beanid="messageSource"class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

<propertyname="basename"value="/WEB-INF/messages/messages"/>

<propertyname="cacheSeconds"value="0"/>

</bean>

<!--ConfiguresSpringMVC

<importresource="mvc-config.xml"/>

-->

</beans>

相关推荐