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

转自:http://www.javaeye.com/topic/737741

开发环境

IDE: eclipse3.4
FrameWork: spring3.0 + spring mvc 3.0 + hibernate 3.2
Server: Tomcat 6.0

使用 OpenSessionInViewFilter的原因

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



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

引用
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition



报错原因:
看过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 配置spring mvc的 controller,viewResolver,messageConvert等

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

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

Xml代码 复制代码
  1. <context:component-scan base-package="com">     
  2.      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    
  3.   </context:component-scan>  



hibernate及数据源的配置

Xml代码 复制代码
  1. <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">  
  2.        <property name="dataSource" ref="dataSource"/>  
  3.        <property name="packagesToScan" value="com"/>  
  4.   
  5.                   
  6.           
  7.        <property name="hibernateProperties">  
  8.             <props>      
  9.   
  10.                    
  11.                    
  12.                 <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>            
  13.                 <!--  <prop key="hibernate.dialect">org.hibernate.dialect.sql</prop>-->  
  14.                 <!-- <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop> -->  
  15.                    
  16.                 <prop key="hibernate.show_sql">true</prop>  
  17.                 <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>  
  18.                 <prop key="hibernate.cache.use_query_cache">true</prop>  
  19.                    
  20.   
  21.                 <prop key="hibernate.hbm2ddl.auto">update</prop>  
  22.                 <prop key="hibernate.format_sql">true</prop>  
  23.                 <prop key="hibernate.use_sql_comments">true</prop>  
  24.   
  25.                 <!--     
  26.                 <prop key="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</prop>  
  27.                 <prop key="hibernate.search.default.indexBase">/WEB-INF/indexes</prop>-->  
  28.             </props>  
  29.         </property>  
  30.      </bean>  



配置transactionManager管理事务

Xml代码 复制代码
  1. <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" autowire="byName"/>  



接下来是 自动化事务

Xml代码 复制代码
  1. <tx:annotation-driven/>  


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

Xml代码 复制代码
  1. <tx:annotation-driven transaction-manager="txManager"/>  



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

Xml代码 复制代码
  1. <context:component-scan base-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

Xml代码 复制代码
  1. <!-- 扫描com及子包,自动实例化带@注释的实例,这里排除@Controller,所有controller的实例化在 mvc-config中完成 -->  
  2. <context:component-scan base-package="com">     
  3.     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    
  4.  </context:component-scan  

>

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

Xml代码 复制代码
  1.  <!-- 扫描com及子包,自动实例化带@controller注释的实例,   
  2.       由于实例化controller时会对controller关联的Service类一同实例化,所以这里需要排除@Service    
  3.  -->  
  4. <context:component-scan base-package="com">  
  5.    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>  
  6.    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>  
  7. </context:component-scan>  




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

Java代码 复制代码
  1. @Service("articleService")   
  2. @Transactional  
  3. public class ArticleService extends GenericService<Article, Long> {   
  4.           
  5.         @Autowired  
  6.     private ArticleDao articleDao;   
  7.   
  8.         @Transactional(readOnly=true)   
  9.         public List<Article> getArticles(){   
  10.           /*...*/  
  11.         }   
  12.            
  13.         @Transactional(propagation=Propagation.REQUIRED)   
  14.         public void saveArticle(long id){   
  15.             /*..*/  
  16.         }   
  17.   
  18.   
  19. }  
@Service("articleService")
@Transactional
public class ArticleService extends GenericService<Article, Long> {
       
        @Autowired
	private ArticleDao articleDao;

        @Transactional(readOnly=true)
        public List<Article> getArticles(){
          /*...*/
        }
        
        @Transactional(propagation=Propagation.REQUIRED)
        public void saveArticle(long id){
            /*..*/
        }


}


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

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

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

Xml代码 复制代码
  1. <!-- 配置aop 切入点 和事务访问策略 -->      
  2. <aop:config>  
  3.   <aop:pointcut id="serviceOperation" expression="execution(* com.*.service..*Service.*(..))"/>    
  4.      <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>  
  5. </aop:config>  
  6.   
  7.   
  8. <tx:advice id="txAdvice" >    
  9.     <tx:attributes>  
  10.         <tx:method name="del*" propagation="REQUIRED"/>  
  11.         <tx:method name="save*" propagation="REQUIRED"/>  
  12.         <tx:method name="update*" propagation="REQUIRED"/>  
  13.         <tx:method name="add*" propagation="REQUIRED"/>  
  14.         <tx:method name="create*" propagation="REQUIRED"/>  
  15.         <tx:method name="get*" read-only="true"/>  
  16.         <tx:method name="*"/>  
  17.     </tx:attributes>  
  18. </tx:advice>    

 

注意
1这部分代码要放在<tx:annotation-driven/>的上面,否则tx:advice不起作用
2<tx:advice id="txAdvice" > 如果前面配置的事务id不是"transactionManager",比如id="txManager",则应该修改为 <tx:advice id="txAdvice" transaction-manager="txManager" >


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

Xml代码 复制代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:context="http://www.springframework.org/schema/context"  
  5.     xmlns:tx="http://www.springframework.org/schema/tx"  
  6.     xmlns:aop="http://www.springframework.org/schema/aop"  
  7.     xsi:schemaLocation="   
  8.         http://www.springframework.org/schema/beans    
  9.         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
  10.         http://www.springframework.org/schema/context    
  11.         http://www.springframework.org/schema/context/spring-context-3.0.xsd   
  12.         http://www.springframework.org/schema/tx   
  13.         http://www.springframework.org/schema/tx/spring-tx-3.0.xsd   
  14.         http://www.springframework.org/schema/aop   
  15.         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">  
  16.       
  17.         
  18.        
  19.    <!-- 扫描com及子包,自动实例化带@注释的实例,这里排除@Controller,所有controller的实例化在 mvc-config中完成 -->  
  20.    <context:component-scan base-package="com">     
  21.        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    
  22.     </context:component-scan>  
  23.        
  24.              
  25.       
  26.    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  27.         <property name="locations">  
  28.             <list>  
  29.                 <value>/WEB-INF/prop/database.properties</value>  
  30.             </list>  
  31.         </property>  
  32.     </bean>    
  33.   
  34.     <!-- JNDI DataSource for J2EE environments  user c3p0-->  
  35.     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">  
  36.         <property name="driverClass"><value>${jdbc.driverClass}</value></property>  
  37.         <property name="jdbcUrl"><value>${jdbc.jdbcUrl}</value></property>         
  38.         <property name="user"><value>${jdbc.user}</value></property>  
  39.         <property name="password"><value>${jdbc.password}</value></property>  
  40.         <property name="minPoolSize"><value>${jdbc.minPoolSize}</value></property>  
  41.         <property name="maxPoolSize"><value>${jdbc.maxPoolSize}</value></property>  
  42.         <property name="maxIdleTime"><value>${jdbc.maxIdleTime}</value></property>  
  43.     </bean>  
  44.        
  45.        
  46.      <!--  hibernate.hbm2ddl.auto参数说明   
  47.                 validate           加载hibernate时,验证创建数据库表结构   
  48.                 create             每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。   
  49.                 create-drop        加载hibernate时创建,退出是删除表结构   
  50.                 update             加载hibernate自动更新数据库结构   
  51.                 none               不更新数据库结构   
  52.       -->       
  53.     <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">  
  54.        <property name="dataSource" ref="dataSource"/>  
  55.        <property name="packagesToScan" value="com"/>  
  56.   
  57.                   
  58.           
  59.        <property name="hibernateProperties">  
  60.             <props>      
  61.   
  62.                    
  63.                    
  64.                 <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>            
  65.                 <!--  <prop key="hibernate.dialect">org.hibernate.dialect.sql</prop>-->  
  66.                 <!-- <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop> -->  
  67.                    
  68.                 <prop key="hibernate.show_sql">true</prop>  
  69.                 <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>  
  70.                 <prop key="hibernate.cache.use_query_cache">true</prop>  
  71.                    
  72.   
  73.                 <prop key="hibernate.hbm2ddl.auto">update</prop>  
  74.                 <prop key="hibernate.format_sql">true</prop>  
  75.                 <prop key="hibernate.use_sql_comments">true</prop>  
  76.   
  77.                 <!--     
  78.                 <prop key="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</prop>  
  79.                 <prop key="hibernate.search.default.indexBase">/WEB-INF/indexes</prop>-->  
  80.             </props>  
  81.         </property>  
  82.      </bean>       
  83.        
  84.     <!-- 配置hibernate事务 -->  
  85.     <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" autowire="byName"/>  
  86.   
  87.       
  88.   
  89.        
  90.        
  91.     <!-- 配置aop 切入点 和事务访问策略 -->      
  92.     <aop:config>  
  93.       <aop:pointcut id="serviceOperation" expression="execution(* com.*.service..*Service.*(..))"/>    
  94.       <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>  
  95.     </aop:config>  
  96.        
  97.        
  98.     <tx:advice id="txAdvice" >    
  99.         <tx:attributes>  
  100.             <tx:method name="del*" propagation="REQUIRED"/>  
  101.             <tx:method name="save*" propagation="REQUIRED"/>  
  102.             <tx:method name="update*" propagation="REQUIRED"/>  
  103.             <tx:method name="add*" propagation="REQUIRED"/>  
  104.             <tx:method name="create*" propagation="REQUIRED"/>  
  105.             <tx:method name="get*" read-only="true"/>  
  106.             <tx:method name="*"/>  
  107.         </tx:attributes>  
  108.     </tx:advice>       
  109.        
  110.     <!-- tx:annotation 自动配置事务,注意这个标签必需放在tx:advice下面,否则不起作用 -->  
  111.     <tx:annotation-driven/>  
  112.        
  113.            
  114.     <!-- Application Message Bundle -->  
  115.     <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">  
  116.         <property name="basename" value="/WEB-INF/messages/messages" />  
  117.         <property name="cacheSeconds" value="0" />  
  118.     </bean>  
  119.   
  120.     <!-- Configures Spring MVC    
  121.     <import resource="mvc-config.xml" />  
  122.     -->  
  123.        
  124.   
  125. </beans>  

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值