說說在 Spring 中,如何基於註解來配置事務
Spring 提供了基於註解的事務配置,即對需要事務增強的 Bean 介面 、 實現類或者方法進行標註@Transactional,然後在容器中配置基於註解的事務增強驅動,即可使用基於註解的宣告式事務 。
1 配置事務示例
我們使用 @Transactional 來為業務類配置事務:
@Service
@Transactional
public class UserService {
@Autowired
private UserDao userDao;
/**
* 新增
*
* @param user
*/
public int addUser(final User user) {
return userDao.save(user);
}
/**
* 依據 Id,獲取賬號
*
* @param userId
* @return
*/
public User getUser(Long userId) {
return userDao.get(userId);
}
/**
* 更新賬號所對應的密碼
* @param userId
* @param pwd
*/
public int update(Long userId,String pwd ) {
return userDao.update(userId,pwd);
}
}
複製程式碼
接著在 Spring 配置檔案中, 告知 Spring 容器對標註了 @Transactional 註解的 Bean,織入事務管理切面:
<!-- 掃描帶 @Transactional 註解的 Bean,織入事務管理切面-->
<tx:annotation-driven transaction-manager="transactionManager"/>
複製程式碼
在預設情況下, <tx:annotation-driven>
會自動使用名為 transactionManager 的事務管理器, 所以,如果我們的事務管理器就叫做 transactionManager ,那麼就可以進一步簡化為
<tx:annotation-driven/>
複製程式碼
<tx:annotation-driven>
擁有以下屬性:
屬性 | 預設值 | 說明 |
---|---|---|
transaction-manager | transactionManager | 事務管理器 Bean ID |
proxy-target-class | false | true 表示將通過建立子類來代理業務類(需要在類路徑中新增 CGlib.jar 類庫); false 表示使用基於介面來代理 。 |
order | - | 如果業務類除了需要事務切面之外,還需要織入其他切面,那麼可以通過該屬性,來控制事務切面在目標連線點中的織入順序。 |
單元測試:
public class UserServiceTest {
ApplicationContext context;
@BeforeMethod
public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("spring_anno.xml");
}
@Test
public void testAddUser() throws Exception {
UserService userService = (UserService) context.getBean("userService");
final User user = new User("deniro");
userService.addUser(user);
}
}
複製程式碼
執行日誌:
從日誌中可以看出,Spring 容器為這個類的所有方法,都織入了事務管理功能。
2 @Transactional 屬性
@Transactional 擁有以下這些屬性:
屬性 | 預設值 | 說明 |
---|---|---|
propagation | PROPAGATION_REQUIRED | 事務傳播行為。可通過org.springframework.transaction.annotation.Propagation 列舉類,來提供合法值,例:@Transactional(propagation=Propagation.SUPPORTS)
|
isolation | ISOLATION_DEFAULT | 事務隔離級別。可通過 org.springframework.transaction.annotation.Isolation 列舉類,來提供合法值,例:@Transactional(isolation=Isolation.READ_UNCOMMITTED)
|
readOnly | false | 是否可讀寫事務。例:@Transactional(readOnly=true)
|
timeout | 使用底層事務系統的預設值 | 超時時間,單位為秒。例: @Transactional(timeout=3)
|
rollbackFor | 回滾所有執行期異常。 | 需要回滾的一組異常類,型別為 Class[], 多個異常類使用逗號分隔。例:@Transactional(rollbackFor={SQLException,class}) 。 |
rollbackForClassName | {} | 需要回滾的一組異常類,型別為 String[]。例:@Transactional(rollbackForClassName={“xxxException”})
|
noRollbackFor | {} | 不需要回滾的一組異常類,型別為 Class<? extends Throwable>[] 。 |
noRolbackForClassName | {} | 不需要回滾的一組異常類,型別為 String[]。 |
3 標註位置
@Transactional 註解可以被標註於介面定義、介面方法 、 類定義和類的 Public 方法上 。
但如果 @Transactional 註解被標註在業務介面上,那麼如果啟用了子類代理:
<tx:annotation-driven proxy-target="true"/>
複製程式碼
那麼被代理的業務類並不會織入事務增強,仍然工作在非事務環境下。這顯然不是我們想看到的。
建議在具體業務類上使用 @Transactional 註解,這樣不管是否開啟子類代理模式,業務類都會織入事務增強。
也可以在直接在方法上定義註解。
方法上定義的註解會覆蓋類定義的註解,比如有些方法需要使用到特殊的事務屬性,那麼就可以直接在方法上定義註解。
在以下示例中,我們在 getUser() 方法上設定了只讀事務屬性:
@Service
@Transactional
public class UserService {
@Autowired
private UserDao userDao;
/**
* 依據 Id,獲取賬號
*
* @param userId
* @return
*/
@Transactional(readOnly = true)
public User getUser(Long userId) {
return userDao.get(userId);
}
...
}
複製程式碼
單元測試:
@Test
public void testGetUser() throws Exception {
UserService userService = (UserService) context.getBean("userService");
User user = userService.getUser(1l);
logger.info("user={}",user);
}
複製程式碼
控制檯輸出結果:
從輸出結果中我們可以看出,在呼叫該方法時,事務加入了只讀屬性。