在描述問題之前先說明幾個前提,假設在Spring的設定檔中使用下面的方式配置了資料庫的事務:
<bean id="transactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager" />
現在有UserDao和SecurityService:
@Repository public class UserDao { public User getUser() { // query user from user table return queryObject("select * from user order by id desc limit 1"); } }
@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; } } } }
在呼叫SecurityService#checkUserInfo()方法的過程中,透過userDao#getUser()方法取得的資料是不變的,即使這個時候新插入了一個name為Tom的資料循環也不會結束。另外將SecurityService上面的@Transactional註解去掉也無濟於事。
首先想到會不會是資料庫連線池的問題,換成了Spring自帶的也是如此;然後從JdbcTemplate裡面直接呼叫了Connection對象,使用原始的JDBC方式操作資料庫,這個時候資料是即時變化的,於是想到應該是Spring的事務和當前操作線程進行綁定了。查看原始程式碼進入之後果然在DataSourceUtils#doGetConnection方法裡面發現了Spring在每個執行緒的每個DataSource上建立了一個Connection並且與交易進行了綁定。因為tx:annotation-driven設定檔對所有的Service層(加了@Service註解的類別)進行了事務綁定,所以無論是否使用@Transactional都在同一個線程中綁定了同一個Connection,只是不進行事務操作而已。
經過多次實驗和查找資料,最後終於找到了完美的解決方法:只要在上述的checkUserInfo方法中加上@Transactional(propagation = Propagation.NOT_SUPPORTED) 註解就可以了。當然也可以取得Connection然後手工進行操作,也可以使用DateUtils套件來操作。