国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

從源碼分析 Spring 基于注解的事務

2019-11-14 23:57:50
字體:
來源:轉載
供稿:網友
源碼分析 SPRing 基于注解的事務

在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")@Transactionalpublic 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 {};}
public @interface Transactional

注解@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; }}
public enum Propagation

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; }}
public enum Isolation

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 的作用。表示有事務則使用當前事務,如果沒有事務,則新建一個只讀事務。

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")@Transactionalpublic 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 (rollbackFor = Exception.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


上一篇:JAVA EE(簡述)

下一篇:JAVA第一個Sample

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 金昌市| 温宿县| 建湖县| 南木林县| 修水县| 宁都县| 乌拉特前旗| 灯塔市| 盐亭县| 苗栗市| 和顺县| 金门县| 拜泉县| 商丘市| 峡江县| 阿城市| 汉中市| 江门市| 广饶县| 桦甸市| 定边县| 甘南县| 云和县| 崇阳县| 南投县| 融水| 敦煌市| 大埔县| 修水县| 巴东县| 青州市| 天长市| 宁津县| 嘉兴市| 阳高县| 大邑县| 沁源县| 平罗县| 沂源县| 两当县| 包头市|