聲明式事務是 Spring 核心優勢之一,但在 SpringMVC + MyBatis 整合中,掃描順序衝突與數據庫引擎不支援事務是兩大高頻踩坑點。本文記錄完整配置流程與核心問題排查。
Spring 容器啟動順序:web.xml → root-context.xml(業務層) → servlet-context.xml(Web 層)。
核心原則:root-context 掃描 Service/DAO,排除 Controller;servlet-context 掃描 Controller,排除 Service。否則 Controller 會注入未經事務增強的原始 Service。
<!-- 掃描 Service、DAO、Component,排除 Controller -->
<context:component-scan base-package="com.sence">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<!-- 數據源配置 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
<!-- MyBatis SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:mapper/**/*.xml" />
<property name="typeAliasesPackage" value="com.sence.entity" />
</bean>
<!-- Mapper 掃描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.sence.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!-- 僅掃描 Controller -->
<context:component-scan base-package="com.sence">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
</context:component-scan>
<!-- MVC 基礎配置 -->
<mvc:annotation-driven />
<mvc:default-servlet-handler />
<!-- 事務管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 切點:Service 實現類所有方法 -->
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* com.sence.service.impl.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods" />
</aop:config>
<!-- 事務屬性定義 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 查詢方法只讀優化 -->
<tx:method name="find*" read-only="true" />
<tx:method name="get*" read-only="true" />
<tx:method name="select*" read-only="true" />
<tx:method name="query*" read-only="true" />
<!-- 寫操作:遇到自定義異常回滾 -->
<tx:method name="save*" rollback-for="BizException" />
<tx:method name="insert*" rollback-for="BizException" />
<tx:method name="update*" rollback-for="BizException" />
<tx:method name="delete*" rollback-for="BizException" />
<!-- 默認:運行時異常回滾 -->
<tx:method name="*" rollback-for="java.lang.RuntimeException" />
</tx:attributes>
</tx:advice>
Service 方法拋出 BizException,日誌顯示進入 doRollback(),但數據庫數據未回滾。
execution(* com.sence.service.impl.*.*(..)) 匹配目標類rollback-for 指定的異常是否為拋出異常的父類PROPAGATION_NEVER 或 NOT_SUPPORTED-- 檢查表引擎
SHOW CREATE TABLE `order`;
-- 修改為 InnoDB
ALTER TABLE `order` ENGINE=InnoDB;
-- 或建表時指定
CREATE TABLE `order` (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
| 存儲引擎 | 事務支援 | 行級鎖 | 外鍵 | 適用場景 |
|---|---|---|---|---|
| InnoDB | ✅ ACID | ✅ | ✅ | 核心業務表、高併發寫入 |
| MyISAM | ❌ | ❌(表鎖) | ❌ | 只讀/日誌/統計表 |
| Memory | ❌ | ❌ | ❌ | 臨時緩存表 |
RuntimeException,系統異常繼承 Exception,明確 rollback-for 策略read-only="true",MyBatis 可跳過脏檢查,數據庫可優化快照讀<tx:method name="*" timeout="30" /> 防止長事務持有鎖READ_COMMITTED + 行鎖,避免間隙鎖死鎖TransactionTemplate 精確控制邊界聲明式事務配置三部曲:掃描隔離正確 → AOP 切點精準 → 底層引擎支援。特別注意:MySQL 表必須使用 InnoDB 引擎,這是事務生效的物理基礎。建立「配置-代碼-數據庫」三層排查思維,可快速定位事務失效根因。