配置 @Transactional(rollbackFor=Exception.class),MySQL 表引擎為 InnoDB,但異常拋出後數據未回滾。
MySQL autocommit 有兩個作用域:
SET SESSION autocommit=0。SET GLOBAL autocommit=0(需 SUPER 權限,重啟失效)。連接池(Druid/HikariCP/DBCP)獲取連接時,若未顯式設置 autocommit=false,會使用數據庫默認值(通常為 ON)。Spring 的 DataSourceTransactionManager 在事務開始時會調用 connection.setAutoCommit(false),但若連接池預先開啟了自動提交,或連接被複用時狀態未重置,事務邊界可能失效。
<!-- Druid 示例 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/db?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="pwd" />
<property name="defaultAutoCommit" value="false" /> <!-- 關鍵 -->
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configurationProperties">
<props>
<prop key="autoCommit">false</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
@Transactional(rollbackFor = Exception.class)
public void testRollback() {
userDao.insert(new User("A"));
int i = 1/0; // 拋出異常
userDao.insert(new User("B"));
}
// 檢查數據庫:應 0 條記錄
@Transactional(readOnly=true) 導致不回滾。rollbackFor(默認僅 RuntimeException/Error)。SHOW TABLE STATUS)。「同時設置 SESSION 與 GLOBAL autocommit=0」治標不治本。核心在於連接池獲取連接時默認 autocommit=false,配合 Spring 事務管理器正確切換狀態。設置 defaultAutoCommit=false 即可徹底解決。