在 Spring 應用中,若在帶有 @Transactional 的 Service 方法中循環調用 DAO 查詢,會發現一個詭異現象:即使數據庫中已插入符合條件的新記錄,循環依然無法檢測到,導致無限循環或邏輯錯誤。本文深入剖析根因並給出最佳解決方案。
@Service
@Transactional
public class SecurityService {
@Autowired
private UserDao userDao;
public void checkUserInfo() {
while (true) {
User user = userDao.getUser(); // 查詢最新用戶
if (user != null && "Tom".equals(user.getName())) {
System.out.println("Tom is here");
break;
}
}
}
}
即使去掉 Service 上的 @Transactional,問題依舊存在。改用 JdbcTemplate 直接操作 Connection 或原生 JDBC 則能正常獲取最新數據。
經過源碼追蹤(DataSourceUtils.doGetConnection)發現:Spring 的 tx:annotation-driven 會為所有標註 @Service 的類開啟事務管理。在同一線程中,Spring 會將同一 DataSource 的 Connection 與當前事務綁定,並存儲在 ThreadLocal 中。
關鍵點:無論方法上是否標註 @Transactional,只要類在事務代理範圍內,該線程就會復用同一個 Connection。這導致:
@Service
@Transactional
public class SecurityService {
@Autowired
private UserDao userDao;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void checkUserInfo() {
while (true) {
User user = userDao.getUser();
if (user != null && "Tom".equals(user.getName())) {
System.out.println("Tom is here");
break;
}
}
}
}
Propagation.NOT_SUPPORTED 表示:以非事務方式運行,掛起當前存在的事務。這樣每次查詢都會獲取新的 Connection,從而讀取到最新提交的數據。
| 方案 | 優點 | 缺點 |
|---|---|---|
NOT_SUPPORTED |
簡潔、聲明式、不侵入業務邏輯 | 需理解事務傳播行為 |
| 手動獲取 Connection | 完全控制 | 代碼繁瑣、易洩漏資源 |
| 獨立非事務 Service | 職責清晰 | 增加類數量、調用麻煩 |
@Transactional(readOnly=true) 或 NOT_SUPPORTED掌握 Spring 事務傳播機制,是解決數據一致性與實時性衝突的關鍵。NOT_SUPPORTED 正是為此類「事務外查詢」場景量身定制的利器。