SpringBoot+MySQL中READ_UNCOMMITTED事务隔离失效问题求助
我用Spring Boot 2.0开发了一个图书目录存储到MySQL 8的应用,图书信息从文件读取。Book对象包含作者列表,图书与作者是多对多关系,因此创建了BOOKS、AUTHORS、AUTHORS_BOOKS三张表。
在事务流程中,我先检查作者是否存在,不存在则插入作者记录;接着存储图书信息;最后将两者ID存入AUTHORS_BOOKS表建立关联。默认auto-commit为true时一切正常,但设置为false后,插入最后一条记录时抛出org.springframework.dao.DataIntegrityViolationException异常,似乎无法读取同一事务内插入的图书和作者ID。
配置信息
application.properties:
spring.datasource.hikari.transaction-isolation=TRANSACTION_READ_UNCOMMITTED spring.datasource.hikari.auto-commit: false spring.datasource.hikari.connection-test-query=SELECT 1 spring.datasource.hikari.pool-name="con-pool" spring.datasource.hikari.minimumIdle=2 spring.datasource.hikari.maximumPoolSize=4 spring.datasource.hikari.idleTimeout=30000 spring.datasource.hikari.maxLifetime=2000000 spring.datasource.hikari.connectionTimeout=60000
核心代码
事务方法:
@Transactional public String storeCatalogEntry(Book book, String serId, short volNum) throws SQLException { List<String> authorsId = storeAuthors(book.getAuthors()); String bookId = storeBookData(book, serId, volNum); storeBookAuthorRefs(bookId, authorsId); return bookId; }
嵌套异常信息:
java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`catalog_db`.`authors_books`, CONSTRAINT `FK_BOOK_AUTHORS` FOREIGN KEY (`BOOK_ID`) REFERENCES `books` (`BOOK_ID`) ON DELETE CASCADE ON UPDATE CASCADE) at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:251)
我尝试给@Transactional添加isolation = Isolation.READ_UNCOMMITTED属性,结果依旧报错。使用Spring JdbcTemplate执行数据库操作的相关代码如下:
private List<String> storeAuthors(Collection<Author> authors) throws SQLException { List<String> ids = new ArrayList<>(authors.size()); for (Author author : authors) { String id = findAuthor(author); if (id == null) { id = storeNewAuthor(author); } ids.add(id); } return ids; } private String storeNewAuthor(Author author) throws SQLException { String id = UUID.randomUUID().toString(); String qry = queries.get("storeAuthor"); try { jdbc.update(qry, (PreparedStatement ps) -> { ps.setString(1, id); if (author.getFirstName() == null) { ps.setNull(2, Types.VARCHAR); } else { ps.setString(2, author.getFirstName()); } ps.setString(3, author.getLastName()); }); } catch (DataAccessException daex) { LOG.error("JDBC error", daex); throw new SQLException(daex); } return id; } private String storeBookData(Book book, String serialId, short volumeNumber) throws SQLException { String id = UUID.randomUUID().toString(); String qry = queries.get("storeBookData"); try { jdbc.update(qry, (PreparedStatement ps) -> { ps.setString(1, id); ps.setString(2, book.getTitle()); ps.setString(3, book.getUrl()); // 省略其他字段设置 ps.setString(8, book.getAnnotation()); ps.setString(9, serialId); ps.setShort(10, volumeNumber); }); } catch (DataAccessException daex) { LOG.error("JDBC error", daex); throw new SQLException(daex); } return id; } private void storeBookAuthorRefs(String bookId, List<String> authorsId) throws SQLException { String qry = queries.get("storeBookAuthorRef"); for (int i = 0; i < authorsId.size(); i++) { String authId = authorsId.get(i); short v = (short) i; try { jdbc.update(qry, (PreparedStatement ps) -> { ps.setString(1, bookId); ps.setString(2, authId); ps.setShort(3, v); }); } catch (DataAccessException daex) { LOG.error("JDBC error", daex); throw new SQLException(daex); } } }
核心原因
- 手动关闭auto-commit导致事务管理冲突:Spring的
@Transactional机制依赖数据源的auto-commit默认配置来接管事务。手动设置auto-commit: false后,Spring无法正确管理事务的提交逻辑,导致同一事务内的插入操作未被及时同步,外键关联时无法找到对应记录。 - 异常回滚规则不匹配:你抛出的
SQLException是检查型异常,Spring默认仅对RuntimeException触发事务回滚,导致异常发生时事务无法正确回滚,进一步加剧数据不一致问题。
解决步骤
恢复Hikari的auto-commit默认配置
移除spring.datasource.hikari.auto-commit: false,让Spring自动管理事务的提交与回滚——Spring开启事务时会自动将连接的auto-commit设为false,事务结束后恢复原有设置,无需手动干预。修正@Transactional的异常回滚配置
在事务方法上添加rollbackFor = SQLException.class,确保SQL异常触发时事务能正确回滚:@Transactional(rollbackFor = SQLException.class) public String storeCatalogEntry(Book book, String serId, short volNum) throws SQLException { // 原有代码不变 }移除不必要的隔离级别配置
TRANSACTION_READ_UNCOMMITTED是最低隔离级别,同一事务内的操作本来就具备可见性,MySQL默认的REPEATABLE READ隔离级别完全满足需求,可移除spring.datasource.hikari.transaction-isolation配置,避免不必要的风险。优化异常处理(可选)
优先使用Spring提供的DataAccessException体系而非原始SQLException,让Spring事务管理更高效地处理异常场景。
内容的提问来源于stack exchange,提问作者Gary Greenberg

