在配置 springmvc+hibernate+mysql 的时候,出现如下问题:
异常信息:
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. org.springframework.orm.hibernate3.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1175) org.springframework.orm.hibernate3.HibernateTemplate$25.doInHibernate(HibernateTemplate.java:839) org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:406) org.springframework.orm.hibernate3.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:374) org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:837) org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:833) com.longxia.springmvc.dao.HibernateBaseDao.deleteObject(HibernateBaseDao.java:94) com.longxia.springmvc.manager.usermanager.UserManagerImpl.deleteUser(UserManagerImpl.java:88) com.longxia.springmvc.controller.UserController.deleteAllUser(UserController.java:52) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) java.lang.reflect.Method.invoke(Unknown Source) org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:436) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:424) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790) org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:669) org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:574) javax.servlet.http.HttpServlet.service(HttpServlet.java:617) javax.servlet.http.HttpServlet.service(HttpServlet.java:717) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
问题原因分析:
为了解决session closed 错误而是用 openSessionInViewInterceptor 或者 openSessionInViewFilter 延迟加载的错误,但是在我们开启OpenSessionInViewFilter这个过滤器的时候FlushMode就已经被默认设置为了MANUAL,如果FlushMode是MANUAL或NEVEL,在操作过程中 hibernate会将事务设置为readonly,所以在增加、删除或修改操作过程中会出现如下错误
解决方案:
1、在执行操作之前 插入getHibernateTemplate().setFlushMode(2) 或者 在方法执行之后 getHibernateTemplate().flush(); 这也能够明白为什么会出现这个原因的。但是本人不推荐这种解决方式
2、使用 hibernateFilter 来解决:
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>flushMode</param-name>
<param-value>AUTO</param-value>
</init-param>
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>sessionFactoryBeanName</param-name>
<param-value>sessionFactory</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>hibernateFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3、配置spring 事务,让spring 来管理hibernate session,有 aop, 有基于注解的;(本人推荐这种方式,因为项目都会有事务的操作)
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory">
</bean>
<!-- 用注解来实现事务管理 将所有具有@Transactional 注解的文件自动配置为声明式事务支持-->
<!--tx:annotation-driven transaction-manager="transactionManager" /-->
<!-- xml配置事务 -->
<tx:advice id="txAdviceHibernate" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethodsHibnerate" expression="execution(* com.longxia.springmvc.manager..*.*(..))"/>
<aop:advisor advice-ref="txAdviceHibernate" pointcut-ref="serviceMethodsHibnerate" />
</aop:config>
<!-- xml配置事务-->
99.9%的人配置了事务之后,都会解决这个问题。但是还有0.1%的人还是悲催的人,哥就是那0.1%的异类。
第一,检查事务的传播级别对不对,
例如 你配的
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
但是你的方法名称是 delInfo(...)..;那么你对应的事务传播级别就是 <tx:method name="*" read-only="true" /> ;还是read-only;
所以你要给你的方法改为 deleteInfo(...);或者在事务的传播级别加上一个<tx:method name="del*" propagation="REQUIRED" />
第二、一个十分蛋疼的问题;你的 manager 到底有没有以bean的形式注入到sping容器里面去,这也是导致我这次问题的原因
由于我使用的是springmvc模式,所以我就用这种方式一起注入 controller,manager,dao
<!-- 激活@Controller模式 -->
<mvc:annotation-driven />
<!-- 对包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 -->
<context:component-scan base-package="com.longxia.**.controller" />
<context:component-scan base-package="com.longxia.**.manager" />
<context:component-scan base-package="com.longxia.**.dao" />
虽然这样访问没有什么问题,在controller里面访问manager,在manager里面访问dao没有什么问题,
但是spring api 上说 :
This tag registers the DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter beans that are required for Spring MVC to dispatch requests to Controllers.
这个标签注册了Spring MVC分发请求到控制器所必须的DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter实例
个人认为这个只是单纯的对controller进行进行分发。并没有将service以bean的形式注入的spring容器中;
所以我将 manager和dao 放在 applicationDataSource.xml文件中以下面这种形式注入spring容器中(<context : annotation-config />):
<context:annotation-config/>
<context:component-scan base-package="com.longxia.**.manager" />
<context:component-scan base-package="com.longxia.**.dao" />
如果觉得放在applicationDataSource.xml里面比较臃肿,可以独立一个applicationContext.xml 将代码复制进去
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
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/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.longxia.**.manager" />
<context:component-scan base-package="com.longxia.**.dao" />
</beans>
然后再web.xml文件中配置applicationContext.xml,引入进去
<!-- 设定配置文件列表 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
<!-- classpath*: 指定编译后的class目录 -->
classpath*:config/application*.xml
</param-value>
</context-param>
这样就可以解决问题了。起码我是这样解决的,哎,困扰了3天。疯掉了。还好今天突发想法。所以分享我的经验,希望后来人早点解决问题。
我的项目结构(只是自己练习的小项目,嘿嘿)
题外话:
关于 mvc:annotation-driven 和 context:annotation-config 这两个区别,我在网上找了一点资料,顺便贴出来供大家分享,(文章出处http://mushiqianmeng.blog.51cto.com/3970029/723880)
在基于主机方式配置Spring的配置文件中,你可能会见到<context:annotation-config/>这样一条配置,他的作用是式地向Spring容器注册
AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、
PersistenceAnnotationBeanPostProcessor以及RequiredAnnotationBeanPostProcessor这4个BeanPostProcessor。
注册这4个BeanPostProcessor的作用,就是为了你的系统能够识别相应的注解。
例如:
如果你想使用@Autowired注解,那么就必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessor Bean。传统声明方式如下:
- <beanclass="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
如果想使用@ Resource、@ PostConstruct、@ PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor
如果想使用@PersistenceContext注解,就必须声明PersistenceAnnotationBeanPostProcessor的Bean。
如果想使用@Required的注解,就必须声明RequiredAnnotationBeanPostProcessor的Bean。同样,传统的声明方式如下:
- <beanclass="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
一般来说,这些注解我们还是比较常用,尤其是Antowired的注解,在自动注入的时候更是经常使用,所以如果总是需要按照传统的方式一条一条配置显得有些繁琐和没有必要,于是spring给我们提供<context:annotation-config/>的简化配置方式,自动帮你完成声明。
不过,呵呵,我们使用注解一般都会配置扫描包路径选项
- <context:component-scanbase-package=”XX.XX”/>
该配置项其实也包含了自动注入上述processor的功能,因此当使用<context:component-scan/>后,就可以将<context:annotation-config/>移除了。