如何修复Spring Boot JPA报错:无法在只读事务中执行INSERT问题
可以通过Java代码层面调整解决该问题,该报错是多数据源场景下事务配置错误导致,无需修改数据库服务端配置。
报错详情:org.springframework.orm.jpa.JpaSystemException : could not execute statement ; nested exception is org.hibernate.exception.GenericJDBCException : Could not execute statement with root cause org.postgresql.util.PSQLException: ERROR cannot execute INSERT in a read-only transaction
该报错本质是执行写操作的事务被标记为只读属性,多数据源场景下常见触发原因有3种:
- 多数据源对应的事务管理器配置错误,混用不同数据源的事务管理器
- 写操作方法/所属类上错误标注了
@Transactional(readOnly = true)注解 - 数据源默认配置了只读的连接属性
1. 配置多数据源独立的事务管理器
每个数据源必须对应独立的PlatformTransactionManager,且在@EnableJpaRepositories注解中绑定对应事务管理器,示例配置如下:
@Configuration @EnableTransactionManagement @EnableJpaRepositories( basePackages = "com.yourproject.repository.second", // 对应数据源的repository包路径 entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "secondTransactionManager" // 绑定对应事务管理器 ) public class SecondDataSourceConfig { // 数据源、EntityManagerFactory配置省略 @Bean public PlatformTransactionManager secondTransactionManager( @Qualifier("secondEntityManagerFactory") EntityManagerFactory entityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory); // 强制关闭事务默认只读配置 transactionManager.setDefaultReadOnly(false); return transactionManager; } }
2. 修正事务注解配置
写操作对应的方法/类上的@Transactional注解必须指定对应数据源的事务管理器,同时明确设置只读属性为false:
// 写方法注解示例 @Transactional(transactionManager = "secondTransactionManager", readOnly = false) public void saveData(DataEntity data) { dataRepository.save(data); }
如果类上标注了全局的@Transactional(readOnly = true),写方法的注解配置会覆盖类上的全局配置。
3. 强制设置连接读写属性
如果以上配置完成后仍存在问题,可以在数据源配置中添加连接初始化SQL,每次获取连接时强制设置为读写模式:
// Hikari连接池示例 @Bean @ConfigurationProperties(prefix = "spring.datasource.second") public DataSource secondDataSource() { HikariDataSource dataSource = new HikariDataSource(); // 连接初始化时执行SQL关闭只读 dataSource.setConnectionInitSql("SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;"); return dataSource; }
如果使用Druid连接池,对应配置参数为connectionInitSqls,传入相同SQL即可。
内容的提问来源于stack exchange,提问作者oxygenan

