异常:Write operations are not allowed in read-only mode (FlushMode.NEVER)

本文探讨了在使用Spring框架时遇到的Writeoperationsarenotallowedinread-onlymode异常,通过调整事务配置和设置FlushMode.AUTO的方式成功解决了问题。
我用Spring的声明事务,增加一个业务功能<关闭问题>时,我出现了异常:
Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition

查了错误原理:在业务层dao.update(quesModel)中getSession的时候,会把获取回来的session的flush mode 设为FlushMode.NEVER。然后把该sessionFactory绑定到 TransactionSynchronizationManager,使request的整个过程都使用同一个session,在请求过后再接除该 sessionFactory的绑定,最后closeSessionIfNecessary 根据该session是否已和transaction绑定来决定是否关闭session。在这个过程中,若HibernateTemplate 发现自当前session有不是readOnly的transaction,就会获取到FlushMode.AUTO Session,使方法拥有写权限。也即是,如果有不是readOnly的transaction就可以由Flush.NEVER转为Flush.AUTO,拥有insert,update ,delete操作权限,如果没有transaction,并且没有另外人为地设flush model的话,则doFilter的整个过程都是Flush.NEVER。所以受transaction保护的方法有写权限,没受保护的则没有。
我的解决方式是:
1.将singleSession设为false,缺点是Hibernate Session的Instance可能会大增,使用的JDBC Connection量也会大增,如果Connection Pool的maxPoolSize设得太小,很容易就出问题。此办法放弃。
2.我的resovedQuesWithNoOkAnswer方法没有写权限,这时方法内有update操作,则需要手动设置flush model为Flush.AUTO,如
session.setFlushMode(FlushMode.AUTO); 
session.save(user);
session.flush();

设置后还是出现同样的问题,不知道是不是Session有没有正确的获取到的原因.
3.Extend OpenSessionInViewFilter,Override protected Session getSession(SessionFactory sessionFactory),将FlushMode直接改为Auto。这种方法很麻烦,并且重写后要重新全部编译。
4.检查事务管理配置(applicationContext.xml)
		<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
<prop key="set*">PROPAGATION_REQUIRED</prop>
<prop key="login*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED, readOnly</prop>
</props>
</property>

我的方法以resoved开头的不可操作数据库(更新数据库),
它默认进行了PROPAGATION_REQUIRED, readOnly方式处理,我手动再添加一个
			<prop key="resoved*">PROPAGATION_REQUIRED</prop>

重启服务,问题就解决了。
<think> 首先,用户提供了一个查询,涉及解决Spring框架中的异常:`org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL)`。异常信息建议将Session转换为FlushMode.COMMIT/AUTO或移除事务定义中的'readOnly'标记。 用户引用了四个参考资料: - 引用[1]:显示了异常的堆栈跟踪。 - 引用[2]:描述了问题场景:在一个service方法中调用多个dao方法,其中查询正常,但update保存方法报错。 - 引用[3]:解释了FlushMode.MANUAL是只读模式,导致写操作失败。 - 引用[4]:提到手动配置Hibernate的sessionFactory,并设置实体类包路径。 用户强调这些引用仅供参考,我需要基于核心问题来回答。核心问题是:如何解决这个异常。 我的响应需要遵循系统级指令: 1. **行内数学表达式**:必须使用$...$格式。但这个问题不涉及数学公式,所以可能不需要使用。如果有变量的引用,确保正确格式。 2. **独立公式**:使用$$...$$并单独成段。同样,不适用。 3. **LaTeX语法正确**:确保任何LaTeX正确。 4. **使用中文回答**:整个响应必须用中文。 5. **生成相关问题**:在响应结束时生成相关问题。 6. **引用标识**:在引用的段落末尾自然地添加引用标识,如[^1]。用户提供了引用,我需要基于这些引用添加标识符。 响应结构: - 参考用户的问题和引用。 - 提供结构清晰的解决方案。 - 确保回答真实可靠:基于我的知识和用户提供的引用。 关键点:异常发生在尝试执行写操作时,但事务被设置为只读模式。FlushMode.MANUAL是Hibernate的只读模式,需要改为COMMIT或AUTO来允许写操作。 可能的解决方案: 1. **修改事务定义**:移除事务的只读标记。在Spring中,事务通常使用@Transactional注解定义。如果设置了readOnly=true,需要改为false。 2. **设置Flush Mode**:在Hibernate配置中,设置flush mode为COMMIT或AUTO。 3. **检查事务传播行为**:确保写操作在正确的事务上下文中。 4. **其他配置**:如手动配置sessionFactory(引用[4]提到)。 基于引用: - 引用[3]:FlushMode.MANUAL是只读的,需要改为COMMIT/AUTO。 - 引用[4]:提到了手动配置sessionFactory。 我的响应应该逐步解释原因和解决方法。 步骤: 1. **理解异常原因**:解释为什么会发生这个异常。 2. **提供解决方案**: - 方法1:修改@Transactional注解,移除readOnly=true或设置为false。 - 方法2:在代码中显式设置flush mode- 方法3:检查事务配置,确保事务管理器正确处理。 - 结合引用[4],涉及手动配置sessionFactory。 3. **举例说明**:提供代码示例。 4. **总结**。 生成相关问题:在响应结束时添加相关问题,类似于示例。 问题生成: 1. 如何避免事务只读模式导致的写操作异常? 2. Spring框架中事务管理的最佳实践是什么? 3. HibernateFlushMode有哪些类型及其用途? 确保响应结构: - 开头:概述问题和原因。 - 主体:详细步骤解决。 - 结尾:生成相关问题。 添加引用标识:在引用用户提供的引用点时添加[^n],例如,当提到FlushMode.MANUAL时引用[^3]。 响应大纲: 1. 介绍问题:"您遇到的异常是由于在只读事务模式下尝试执行写操作导致的..." 2. 原因分析:"根据引用[3],FlushMode.MANUAL..." 3. 解决方案列表: - 修改事务注解。 - 设置flush mode- 配置sessionFactory。 4. 代码示例:提供简单的代码片段。 5. 结束语。 6. 生成相关问题。 代码示例:使用Java代码展示如何修改@Transactional。 </think> ### 解决 org.springframework.dao.InvalidDataAccessApiUsageException 异常的方法 您遇到的异常 `org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL)` 是由于在 Spring 事务管理中,操作被错误地设置为只读模式(`FlushMode.MANUAL`),而您尝试执行了写操作(如更新或保存)。这种模式禁止任何数据修改操作,导致 Hibernate 抛出异常异常信息建议将会话的 flush mode 改为 `COMMIT` 或 `AUTO`,或移除事务定义中的 "readOnly" 标记。以下是基于引用资料的逐步解决方法,我们将分析原因并提供具体方案。 #### 原因分析 异常的核心在于事务配置问题: - 在只读事务模式下(`FlushMode.MANUAL`),Hibernate 会话不允许执行写操作(如 `update` 或 `save`),但允许读操作(如查询)。这通常是由于事务定义中显式设置了 `readOnly=true` 或使用了默认配置的只读模式[^3]。 - 问题常见于 Spring 的声明式事务管理(如 `@Transactional` 注解)。当您在同一个 Service 方法中混合读操作和写操作时,如果事务被标记为只读,写操作就会失败。引用[2]提到,调用查询方法正常但 update 方法报错,这表明事务可能被错误地设置为只读[^2]。 - FlushMode 控制 Hibernate 会话如何同步数据到数据库: - `MANUAL`:只读模式,手动触发刷新(禁止写操作)。 - `COMMIT`:在事务提交时自动刷新(允许写操作)。 - `AUTO`:在每次操作后自动刷新(允许写操作)。 默认配置或错误的注解可能导致会话进入 `MANUAL` 模式[^3]。 #### 解决方法 以下是结构清晰的解决方案,分为代码层和配置层。实施时,请根据您的项目上下文进行调整。每个方案都优先推荐引用资料中的建议。 1. **修改事务注解(移除或调整 "readOnly" 标记)** 这是最常见且推荐的方法。检查 Service 层方法的 `@Transactional` 注解,确保包含写操作的方法未设置 `readOnly=true`。将其改为 `readOnly=false` 或移除该属性(默认允许写操作)。 - **步骤**: - 识别涉及写操作的 Service 方法(如引用[2]中调用 update 保存的方法)。 - 移除 `readOnly=true` 或将属性设置为 `false`。 - 示例代码: ```java // 错误:设置了 readOnly=true @Transactional(readOnly = true) public void myServiceMethod() { dao.queryMethod(); // 读操作正常 dao.updateMethod(); // 写操作报错 } // 正确:移除 readOnly 或设置为 false @Transactional // 默认 readOnly=false,允许写操作 // 或 @Transactional(readOnly = false) public void myServiceMethod() { dao.queryMethod(); dao.updateMethod(); // 写操作正常 } ``` - **原因**: `@Transactional` 的 `readOnly` 属性控制会话的 flush mode。移除后,会话会自动使用 `FlushMode.AUTO` 或 `COMMIT`(取决于 Hibernate 配置),从而允许写操作[^1][^3]。 2. **显式设置 Flush Mode 为 COMMIT 或 AUTO** 如果事务注解无法修改(例如,共享事务定义),可以在代码中动态更改 Hibernate 会话的 flush mode。引用[3]强调,需将 `FlushMode.MANUAL` 改为 `COMMIT` 或 `AUTO`[^3]。 - **步骤**: - 在 DAO 层或 Service 层的写操作前,注入 `SessionFactory` 并手动设置 flush mode- 示例代码: ```java import org.hibernate.Session; import org.hibernate.FlushMode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate5.HibernateTemplate; @Repository public class MyDao { @Autowired private HibernateTemplate hibernateTemplate; public void updateMethod() { // 获取会话并设置 flush mode Session session = hibernateTemplate.getSessionFactory().getCurrentSession(); session.setHibernateFlushMode(FlushMode.AUTO); // 或 FlushMode.COMMIT // 执行写操作 // ... update or save logic ... } } ``` - **注意**:此方法适用于细粒度控制,但可能导致代码冗余。确保在事务边界内操作,避免会话管理问题。 3. **检查并配置事务管理器或 SessionFactory** 全局配置错误也可能导致此问题。引用[4]提到手动配置 `sessionFactory`,确保其正确扫描实体类并设置 flush mode[^4]。 - **步骤**: - 检查 Spring 配置文件(如 XML 或 Java Config),事务管理器是否默认设置了只读模式。 - 在 `sessionFactory` bean 中设置默认 flush mode 为 `AUTO`。 - 示例配置(使用 Java Config): ```java @Configuration @EnableTransactionManagement public class HibernateConfig { @Bean public LocalSessionFactoryBean sessionFactory() { LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setPackagesToScan("com.example.entities"); // 引用[4]建议设置实体类包路径 // 设置默认 flush mode Properties hibernateProperties = new Properties(); hibernateProperties.setProperty("hibernate.flushMode", "AUTO"); // 改为 AUTO 或 COMMIT sessionFactory.setHibernateProperties(hibernateProperties); return sessionFactory; } @Bean public HibernateTransactionManager transactionManager() { HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(sessionFactory().getObject()); return txManager; } } ``` - **原因**:全局配置确保了所有会话默认使用允许写操作的 flush mode,避免了手动修改每个方法的事务属性。 4. **检查事务传播行为(可选)** 如果方法嵌套调用事务方法,确保传播行为正确(如 `PROPAGATION_REQUIRED`)。如果写操作在只读事务的子事务中,也可能触发异常- **示例调整**: ```java @Transactional(propagation = Propagation.REQUIRED) // 确保传播行为支持写操作 public void parentMethod() { childReadMethod(); childWriteMethod(); } ``` #### 预防措施 - **测试与调试**:在调试时,使用 Spring 的 `@Transactional` 属性检查工具(如设置 `rollbackFor`)来验证事务模式。 - **最佳实践**:避免在同一个方法中混合只读和写操作的事务注解。将读操作和写操作分离到不同方法中。 - **性能影响**:设置 `FlushMode.AUTO` 可能增加数据库交互频率,但对于大多数应用是可接受的。如果性能敏感,使用 `COMMIT` 模式。 通过以上方法,您可以从根源上解决异常。如果问题依旧,检查日志确认事务启动细节或升级 Spring/Hibernate 版本(引用资料提示可能涉及框架版本兼容性)[^1][^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值