1. 程式人生 > 程式設計 >說說在 Spring 中,如何基於註解來配置事務

說說在 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);
    }
複製程式碼

控制檯輸出結果:

image.png

從輸出結果中我們可以看出,在呼叫該方法時,事務加入了只讀屬性。