在spring引入基於注解的事務(@Transactional)之前,我們一般都是如下這樣進行攔截事務的配置:
<!-- 攔截器方式配置事務 --> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="append*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="modify*" propagation="REQUIRED" /> <tx:method name="edit*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="repair" propagation="REQUIRED" /> <tx:method name="delAndRepair" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" /> <tx:method name="find*" propagation="SUPPORTS" /> <tx:method name="load*" propagation="SUPPORTS" /> <tx:method name="search*" propagation="SUPPORTS" /> <tx:method name="datagrid*" propagation="SUPPORTS" /> <tx:method name="*" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* net.aazj.service..*Impl.*(..))" /> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" /> </aop:config>
這種方式明顯的缺點是,不太容易理解,並且限定了service層的方法名稱的前綴,沒有模板的話寫起來也很難,很容易寫錯。
因此在spring中引入了基於注解的事務配置方法之后,我們應該拋棄這種事務配置方法了。基於注解 @Transactional 的事務配置具有簡單,靈活的優點。下面看一個例子:
@Service("userService") @Transactional public class UserServiceImpl implements UserService{ @Autowired private UserMapper userMapper; @Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=true) public User getUser(int userId) { return userMapper.getUser(userId); } @Transactional public void addUser(String username){ userMapper.addUser(username); // int i = 1/0; // 測試事務的回滾 } @Transactional (rollbackFor = BaseBusinessException.class) public void addAndDeleteUser(String username, int id) throws BaseBusinessException{ userMapper.addUser(username); this.m1(); userMapper.deleteUserById(id); } private void m1() throws BaseBusinessException { throw new BaseBusinessException("xxx"); } }
首先在service類上聲明了@Transactional,表明類中的所有方法都需要運行在事務中,然后在方法中可以指定具體的事務特性,方法中的@Transactional會覆蓋類上的@Transactional。
下面我們從源碼的角度(從源碼的學習可以給我們實打實的比較深入理解,而且不會出錯,二手資料總是會有時延的)來探究一下它們:

public @interface Transactional { /** * A qualifier value for the specified transaction. * <p>May be used to determine the target transaction manager, * matching the qualifier value (or the bean name) of a specific * {@link org.springframework.transaction.PlatformTransactionManager} * bean definition. */ String value() default ""; /** * The transaction propagation type. * Defaults to {@link Propagation#REQUIRED}. * @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior() */ Propagation propagation() default Propagation.REQUIRED; /** * The transaction isolation level. * Defaults to {@link Isolation#DEFAULT}. * @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel() */ Isolation isolation() default Isolation.DEFAULT; /** * The timeout for this transaction. * Defaults to the default timeout of the underlying transaction system. * @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout() */ int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; /** * {@code true} if the transaction is read-only. * Defaults to {@code false}. * <p>This just serves as a hint for the actual transaction subsystem; * it will <i>not necessarily</i> cause failure of write access attempts. * A transaction manager which cannot interpret the read-only hint will * <i>not</i> throw an exception when asked for a read-only transaction. * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly() */ boolean readOnly() default false; /** * Defines zero (0) or more exception {@link Class classes}, which must be a * subclass of {@link Throwable}, indicating which exception types must cause * a transaction rollback. * <p>This is the preferred way to construct a rollback rule, matching the * exception class and subclasses. * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)} */ Class<? extends Throwable>[] rollbackFor() default {}; /** * Defines zero (0) or more exception names (for exceptions which must be a * subclass of {@link Throwable}), indicating which exception types must cause * a transaction rollback. * <p>This can be a substring, with no wildcard support at present. * A value of "ServletException" would match * {@link javax.servlet.ServletException} and subclasses, for example. * <p><b>NB: </b>Consider carefully how specific the pattern is, and whether * to include package information (which isn't mandatory). For example, * "Exception" will match nearly anything, and will probably hide other rules. * "java.lang.Exception" would be correct if "Exception" was meant to define * a rule for all checked exceptions. With more unusual {@link Exception} * names such as "BaseBusinessException" there is no need to use a FQN. * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)} */ String[] rollbackForClassName() default {}; /** * Defines zero (0) or more exception {@link Class Classes}, which must be a * subclass of {@link Throwable}, indicating which exception types must <b>not</b> * cause a transaction rollback. * <p>This is the preferred way to construct a rollback rule, matching the * exception class and subclasses. * <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)} */ Class<? extends Throwable>[] noRollbackFor() default {}; /** * Defines zero (0) or more exception names (for exceptions which must be a * subclass of {@link Throwable}) indicating which exception types must <b>not</b> * cause a transaction rollback. * <p>See the description of {@link #rollbackForClassName()} for more info on how * the specified names are treated. * <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)} */ String[] noRollbackForClassName() default {}; }
注解@Transactional的屬性有:propagation, isolation, timeout, readOnly, rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName
propagation, isolation, timeout, readOnly都有默認值,而rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName默認值都是空的。
我們具體看下我們可能會用到的屬性:propagation, isolation, readOnly, rollbackFor
1)propagation指定事務的傳播屬性:

public enum Propagation { /** * Support a current transaction, create a new one if none exists. * Analogous to EJB transaction attribute of the same name. * <p>This is the default setting of a transaction annotation. */ REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: For transaction managers with transaction synchronization, * PROPAGATION_SUPPORTS is slightly different from no transaction at all, * as it defines a transaction scope that synchronization will apply for. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) * will be shared for the entire specified scope. Note that this depends on * the actual synchronization configuration of the transaction manager. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** * Support a current transaction, throw an exception if none exists. * Analogous to EJB transaction attribute of the same name. */ MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), /** * Create a new transaction, and suspend the current transaction if one exists. * Analogous to the EJB transaction attribute of the same name. * <p>Note: Actual transaction suspension will not work out-of-the-box on * all transaction managers. This in particular applies to JtaTransactionManager, * which requires the {@code javax.transaction.TransactionManager} to be * made available it to it (which is server-specific in standard J2EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** * Execute non-transactionally, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: Actual transaction suspension will not work on out-of-the-box * on all transaction managers. This in particular applies to JtaTransactionManager, * which requires the {@code javax.transaction.TransactionManager} to be * made available it to it (which is server-specific in standard J2EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** * Execute non-transactionally, throw an exception if a transaction exists. * Analogous to EJB transaction attribute of the same name. */ NEVER(TransactionDefinition.PROPAGATION_NEVER), /** * Execute within a nested transaction if a current transaction exists, * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB. * <p>Note: Actual creation of a nested transaction will only work on specific * transaction managers. Out of the box, this only applies to the JDBC * DataSourceTransactionManager when working on a JDBC 3.0 driver. * Some JTA providers might support nested transactions as well. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */ NESTED(TransactionDefinition.PROPAGATION_NESTED); private final int value; Propagation(int value) { this.value = value; } public int value() { return this.value; } }
propagation可以取如下的值:REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED
我們一般會只用:REQUIRED(默認值), SUPPORTS,其它的取值基本上不使用(具體可以參考上面源碼中的注釋,已經很詳細了)。
propagation=Propagation.REQUIRED:表示該方法或類必須要事務的支持,如果已經是在一個事務中被調用,那么就使用該事務,如果沒有在一個事務中,那么就新建一個事務。
propagation=Propagation.SUPPORTS:表示該方法或類支持事務,如果已經是在一個事務中被調用,那么就使用該事務,如果沒有在一個事務中,也可以。
2)isolation指定事務的隔離級別:

public enum Isolation { /** * Use the default isolation level of the underlying datastore. * All other levels correspond to the JDBC isolation levels. * @see java.sql.Connection */ DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), /** * A constant indicating that dirty reads, non-repeatable reads and phantom reads * can occur. This level allows a row changed by one transaction to be read by * another transaction before any changes in that row have been committed * (a "dirty read"). If any of the changes are rolled back, the second * transaction will have retrieved an invalid row. * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED */ READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), /** * A constant indicating that dirty reads are prevented; non-repeatable reads * and phantom reads can occur. This level only prohibits a transaction * from reading a row with uncommitted changes in it. * @see java.sql.Connection#TRANSACTION_READ_COMMITTED */ READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), /** * A constant indicating that dirty reads and non-repeatable reads are * prevented; phantom reads can occur. This level prohibits a transaction * from reading a row with uncommitted changes in it, and it also prohibits * the situation where one transaction reads a row, a second transaction * alters the row, and the first transaction rereads the row, getting * different values the second time (a "non-repeatable read"). * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ */ REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), /** * A constant indicating that dirty reads, non-repeatable reads and phantom * reads are prevented. This level includes the prohibitions in * {@code ISOLATION_REPEATABLE_READ} and further prohibits the situation * where one transaction reads all rows that satisfy a {@code WHERE} * condition, a second transaction inserts a row that satisfies that * {@code WHERE} condition, and the first transaction rereads for the * same condition, retrieving the additional "phantom" row in the second read. * @see java.sql.Connection#TRANSACTION_SERIALIZABLE */ SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); private final int value; Isolation(int value) { this.value = value; } public int value() { return this.value; } }
isolation可以取如下的值:DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
我們一般只使用:DEFAULT(默認值),因為我們一般是直接在數據庫的層面上來設置事務的隔離級別,很少會在應用層來設置隔離基本。
isolation=Isolation.DEFAULT:表示使用下層數據庫指定的隔離級別
3)readOnly為只讀,利於數據庫優化器進行優化。默認值為 false。所以對於只對數據庫進行讀取的方法,我們可以如下指定:
@Transactional (propagation=Propagation.SUPPORTS) public User getUser(int userId) { return userMapper.getUser(userId); }
表示:有事務則使用當前事務,沒有事務則不使用事務。最大限度的利於數據庫的優化器進行優化。
如果一定要使用事務的話,也可以這樣使用readOnly=true來優化:
@Transactional (propagation=Propagation.REQUIRED,readOnly=true) public User getUser(int userId) { return userMapper.getUser(userId); }
這就是 readOnly 的作用。表示有事務則使用當前事務,如果沒有事務,則新建一個只讀事務。其實 上面的 propagation=Propagation.REQUIRED也是可以去掉的,因為他是默認值!
@Transactional (readOnly=true) public User getUser(int userId) { return userMapper.getUser(userId); }
表示強制事務,並且是 只讀事務。readOnly 要注意有點的是,readOnly只有在 有事務時,才會生效,如果沒有事務,那么 readOnly 是會被忽略的。比如:
@Transactional(propagation=Propagation.SUPPORTS, readonly=true)
表示的就是 沒有事務 也是可以的,有事務的話,就一定是 只讀事務。
4)rollbackFor,其實該屬性也很少使用,而且經常被誤用。表示拋出什么異常時,會回滾事務。異常分為受檢異常(必須進行處理或者重新拋出)和非受檢異常(可以不進行處理)。在遇到非受檢異常時,事務是一定會進行回滾的。rollbackFor用於指定對於何種受檢異常發生時,進行回滾。因為受檢異常,我們必須進行處理或者重新拋出,所以只有一種情況下我們要使用rollbackFor來指定,就是我們不處理異常,直接拋出受檢異常,並且我們需要方法在拋出該異常時,進行回滾,如上面的例子:
@Transactional (rollbackFor = BaseBusinessException.class) public void addAndDeleteUser(String username, int id) throws BaseBusinessException{ userMapper.addUser(username); this.m1(); userMapper.deleteUserById(id); } private void m1() throws BaseBusinessException { throw new BaseBusinessException("xxx"); }
因為 m1 方法拋出受檢異常,我們在 addAndDeleteUser 方法中不對該異常進行處理,而是直接拋出,如果我們希望 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 要么都成功,要么都失敗,此時我們則應該指定:rollbackFor = BaseBusinessException.class ,進行回滾。
所以只有我們的方法聲明要拋出一個受檢異常時,我們才應該使用 rollbackFor 屬性來進行處理。如果我們在 addAndDeleteUser 方法中對 m1 方法的受檢異常進行了處理,那么就沒有必要使用 rollbackFor 了:
public void addAndDeleteUser(String username, int id){ userMapper.addUser(username); try{ this.m1(); }catch(BaseBusinessException e){ // 處理異常,比如記錄進日志文件等 } userMapper.deleteUserById(id); }
因為我們處理了 m1 方法的異常,那么就不會有受檢異常導致 userMapper.addUser(username) 和 userMapper.deleteUserById(id) 這兩個方法一個執行成功,一個沒有執行。而非受檢異常默認就會回滾。
受檢異常是必須進行處理或者重新聲明拋出的。只有聲明重新拋出受檢異常時,才會需要使用 rollbackFor 屬性。所以下面的方式就屬於濫用 rollbackFor 了:
@Transactional (rollbackFor=Exception.class) public void addAndDeleteUser2(String username, int id){ userMapper.addUser(username); userMapper.deleteUserById(id); }
因為:受檢異常是必須進行處理或者重新聲明拋出,而我們既沒有進行處理,也沒有重新拋出,就說明他絕對不可能會拋出受檢異常了。而只會拋出未受檢異常,而未受檢異常,默認就會回滾,所以上面的 @Transactional (rollbackFor=Exception.class) 完全是多余的。
總結:
1)@Transactional 的默認值為:@Transactional (propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false,
timeout=TransactionDefinition.TIMEOUT_DEFAULT),默認值已經適合絕大多數情況,所以我們一般使用 @Transactional 進行注解就夠了。
2)只有當默認值不符合我們的需要時才給@Transactional的屬性指定值,一般也就指定:propagation=Propagation.SUPPORTS 和 readOnly=true,其它的屬性和值一般很少使用非默認值。所以我們前面的UserServiceImpl類可以重構如下:
@Service("userService") @Transactional public class UserServiceImpl implements UserService{ @Autowired private UserMapper userMapper; @Transactional (readOnly=true) public User getUser(int userId) { return userMapper.getUser(userId); } public void addUser(String username){ userMapper.addUser(username); int i = 1/0; // 測試事務的回滾 } public void deleteUser(int id){ userMapper.deleteUserById(id); // int i = 1/0; // 測試事務的回滾 } @Transactional (rollbackFor = BaseBusinessException.class) public void addAndDeleteUser(String username, int id) throws BaseBusinessException{ userMapper.addUser(username); this.m1(); userMapper.deleteUserById(id); } private void m1() throws BaseBusinessException { throw new BaseBusinessException("xxx"); } }
addUser 和 deleteUser 因為會繼承上的 @Transactional ,所以無需另外指定了,只有當類上指定的 @Transactional 不適合時,才需要另外在方法上進行指定。
3)所以我們只實際情況中,我們只需要使用下面三者來進行注解事務的配置:
@Transactional,
@Transactional (readOnly=true),
@Transactional (propagation=Propagation.SUPPORTS, readOnly=true),
@Transactional (rollbackFor = xxException.class),其它都可以保持默認值,其它的非默認值極少使用。
另外:
要使用@Transactional來進行注解事務配置,必須要在spring的配置文件中加入下面的配置說明,啟用基於注解的配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 使用annotation定義事務 -->
<tx:annotation-driven transaction-manager="transactionManager" />
補充說明:
也許你會覺得奇怪,userMapper.getUser(userId); 這些涉及到數據庫訪問的方法,為什么不會拋出 SQLException 受檢異常呢?如果手動注冊驅動,然后獲取鏈接,進行數據庫操作時,是會有許多的受檢異常要處理的:
try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { // ... } String url="jdbc:mysql://localhost:3306/databasename"; try { Connection conn = (Connection) DriverManager.getConnection(url,"username","password"); } catch (SQLException e) { // ... }
哪為什么在這里userMapper.getUser(userId); 就不需要處理受檢異常呢?其實這是spring的功能,spring為了將我們從那些十分麻煩的受檢異常比如SQLException中解救處理,將所有的數據庫訪問層的受檢異常轉嫁到 Spring 的 非受檢異常RuntimeException 體系中來——DataAccessException。