事務管理兩大範式
- 程式化事務:在業務代碼中顯式控制
commit()/rollback(),靈活但侵入性強、樣板代碼多。
- 聲明式事務:透過 AOP 將事務邏輯與業務邏輯分離,僅需配置或註解,推薦生產使用。
方案一:純 XML 配置(AOP 命名空間)
<!-- 1. 事務管理器(Hibernate 5 範例) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<!-- 2. 事務屬性定義(Advice) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="REQUIRED" read-only="true"/>
<tx:method name="find*" propagation="REQUIRED" read-only="true"/>
<tx:method name="query*" propagation="REQUIRED" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 3. AOP 切面綁定 -->
<aop:config>
<aop:pointcut id="daoPointcut" expression="execution(* com.example.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="daoPointcut"/>
</aop:config>
關鍵點解析
tx:advice 定義事務語義(傳播行為、隔離級別、超時、唯讀)。
aop:pointcut 用 AspectJ 表達式精準攔截目標方法(此處為 DAO 層所有方法)。
aop:advisor 將 Advice 與 Pointcut 綁定,完成 AOP 織入。
- 優點:零侵入,業務代碼完全無 Spring 依賴;缺點:XML 冗長,修改需改配置。
方案二:XML + @Transactional 註解(推薦)
<!-- 1. 事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<!-- 2. 開啟註解驅動 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
註解使用規範
@Service
@Transactional(readOnly = true) // 類級別預設唯讀
public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void register(User user) {
userDao.insert(user);
// 其他業務邏輯...
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User getById(Long id) {
return userDao.selectById(id);
}
}
註解放置位置關鍵規則
@Transactional 必須標註在「呼叫 DAO 的那一層」(通常是 Service 層),而非 DAO 層本身。 原因:Spring AOP 預設使用 JDK 動態代理(介面代理),只有透過代理物件呼叫方法時,事務攔截器才會生效。若在 DAO 實作類上加註解,且 Service 直接注入 DAO 實作類(非介面),則事務不生效。
兩種方案對比
| 維度 | 純 XML | XML + 註解 |
| 耦合度 | 低(業務代碼零 Spring 依賴) | 中(引入 Spring 註解 API) |
| 可讀性 | 需對照 XML 與代碼 | 代碼即文檔,直觀 |
| 維護性 | 修改配置檔,不動代碼 | 修改註解,重編譯部署 |
| 靈活度 | 統一攔截規則,難以針對單一方法微調 | 方法級精細控制 |
| 推薦場景 | 嚴格分層、團隊規範統一、遺專案 | 現代 Spring 應用、微服務、敏捷開發 |
常見坑點避雷
- 自呼叫失效:同一類內部方法互呼叫繞過代理,解決:
AopContext.currentProxy()、拆分類、使用 AspectJ 編譯時期織入。
- 異常吞沒導致不回滾:預設僅回滾
RuntimeException、Error,檢查異常需顯式 rollbackFor = Exception.class。
- 傳播行為誤用:
REQUIRES_NEW 開啟獨立事務,若外層回滾不影響內層,慎用。
- 資料源未配置事務管理器:多資料源場景需指定
@Transactional("txManagerOrder")。

