SpringMVC+Spring+Hibernate集成问题
用 SpringMVC + Spring + Hibernate 搭 SSH 时,最常见的一类坑是:HibernateTemplate 一执行 save / update 就抛 InvalidDataAccessApiUsageException,提示写操作在只读模式(FlushMode.MANUAL)下不被允许。
根因通常不是 Hibernate 本身坏了,而是 OpenSessionInViewFilter 把 Session 的 flush 模式钉死了,而业务方法又没被声明式事务保护。下面按「现象 → 原因 → 三种改法 → 事务与扫描」把这条链路走完。
一、报错现象
项目启动后,只要走到写库操作,堆栈会落到 HibernateTemplate.checkWriteOperationAllowed:
中文意思就是:当前 Session 处于只读 / FlushMode.MANUAL。要么把 Session 改成 FlushMode.COMMIT / AUTO,要么去掉事务定义上的 readOnly 标记。
二、原因:OpenSessionInView 把 Session 绑成只读
OpenSessionInViewFilter 在 getSession 时,会把拿到的 Session 的 flush mode 设为 FlushMode.NEVER(新版本对应 MANUAL),再把 sessionFactory 绑到 TransactionSynchronizationManager。这样整个 request 共用同一个 Session,请求结束后再解绑,并按是否已与 transaction 绑定来决定要不要 closeSession。
HibernateTemplate 只有发现当前 Session 上存在非 readOnly 的事务 时,才会把 flush 切到 FlushMode.AUTO,从而允许 insert / update / delete。没有事务、也没有人为改 flush mode,整个 doFilter 过程就一直是 Flush.NEVER。
所以:受声明式事务保护的方法有写权限,没被保护的方法没有。SSH 集成时最容易踩的,是 Controller 和 Service 被同一套 component-scan 扫进去,事务切面绑错层。
三、三种解决办法
| 办法 | 做什么 | 适用 |
|---|---|---|
| 方法一:拆开扫描 | spring-servlet.xml 只扫 @Controller;applicationContext.xml 排除 @Controller | 推荐,职责清晰 |
| 方法二:execute 回调 | 不用 HibernateTemplate.save,改成 session.save | 临时绕过 flush 检查 |
| 方法三:合并配置 | 把 spring-servlet.xml 和 applicationContext.xml 合成一份 | 小项目能用,不推荐长期 |
方法一:父子容器各扫各的
SpringMVC 的 DispatcherServlet 和根容器 ContextLoaderListener 是两套容器。父容器管 Service / Dao / 事务,子容器管 Controller。扫描范围重叠时,Controller 会在子容器里再 new 一份,事务切面绑不上。
spring-servlet.xml 只扫控制器:
applicationContext.xml 扫业务层,排除控制器:
方法二:HibernateCallback 自己拿 Session
原来是 getHibernateTemplate().save(user),改成 execute 回调:
方法三:合并两个 xml
把 spring-servlet.xml 和 applicationContext.xml 合成一份,父子容器问题会消失,但配置职责会糊在一起。
四、事务怎么配
1. 代码开事务
原文写法里的 beginTrasaction / getTrasaction 是拼写错误,正确 API 是:
2. 声明式事务(tx:advice)
Hibernate4 下 getCurrentSession() 必须处在事务中。查询方法即便只读,也要给 REQUIRED,同时可标 read-only:
3. 注解事务
五、component-scan 怎么用
xml 里配了 <context:component-scan base-package="com.ittx.spring002" /> 之后,Spring 会扫该包及子包,遇到 @Component、@Controller、@Service 等就注册成 bean。
配了 component-scan 就不必再写 <context:annotation-config/>,前者已经包含后者。它还有两个子标签:
use-default-filters 默认是 true,会扫描指定包下全部带 @Component 及其派生注解(@Service、@Repository 等)的类。如果只想按 include-filter 精确扫,把它改成 false。
一句话总结:SSH 写库报 FlushMode.MANUAL,优先检查 OpenSessionInView 叠加事务切面是否绑到 Service;父子容器扫描重叠时,用 include/exclude 把 Controller 和业务层拆开。
转载请注明来源:SpringMVC+Spring+Hibernate集成问题






