前言:
事務回滾我們總是在用到,但是有可能不太瞭解具體的細節,接下來我會通過源碼解讀以及真實的案例測試,來說明。
接下來我會從三方面來講述事務的運用:
- 源碼解讀
- 事務使用
- 事務的失效場景
1.源碼解讀
/**
描述事務的屬性在一個方法或者類上(個人覺得應該是使用事務的屬性在方法或者類上)
*<p>
這種註釋類型通常可以直接與Spring的註釋類型進行比較,
實際上將直接將數據轉換爲後者,因此Spring的事務支持代碼不必知道註釋。 如果沒有規則與異常相關,則將其視爲回滾運行時異常
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* 主要是spring不提供事務管理器,可以通過這個去定義自己想要的事務管理器,例如hibernate的、JTA的、JDBC的
* 大致上有以下幾種
* org.springframework.jdbc.datasource.DataSourceTransactionManager DBC及iBATIS、MyBatis框架事務管理器
* org.springframework.orm.jdo.JdoTransactionManager Jdo事務管理器
* org.springframework.orm.jpa.JpaTransactionManager Jpa事務管理器
* org.springframework.orm.hibernate3.HibernateTransactionManager hibernate事務管理器
* org.springframework.transaction.jta.JtaTransactionManager Jta事務管理器
*/
String value() default "";
/**
* * 事務傳播類型 默認是required 具體解釋如下:
* @Transactional(propagation=Propagation.REQUIRED) :如果有事務, 那麼加入事務, 沒有的話新建一個(默認情況下)
* @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不爲這個方法開啓事務
* @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
* @Transactional(propagation=Propagation.MANDATORY) :必須在一個已有的事務中執行,否則拋出異常
* @Transactional(propagation=Propagation.NEVER) :必須在一個沒有的事務中執行,否則拋出異常(與Propagation.MANDATORY相反)
* @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean調用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務.
* <p>
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事務的隔離機制 默認是數據庫的隔離機制 需要是innodb引擎數據庫纔可以
* READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), 讀取未提交數據(會出現髒讀, 不可重複讀) 基本不使用
* READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), 讀取已提交數據(會出現不可重複讀和幻讀)
* REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), 可重複讀(會出現幻讀)
* SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); 串行化
*/
Isolation isolation() default Isolation.DEFAULT;
/*
* 事務超時時間 默認-1 永遠不超時 如果設置之後 達到時間自動回滾
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 設置是否只讀 默認是false 如果設置之後會鎖住庫 如果有插入的話 會報異常 java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
*/
boolean readOnly() default false;
/**
* 回滾的異常 默認只能是回滾runtimeExceptional
如果想全部回滾需要加上 rollback=exceptional.class
* CheckedException不回滾:
* Java認爲Checked異常都是可以被處理的異常,所以Java程序必須顯式的處理Checked異常,如果程序沒有處理checked異常,程序在編譯時候將發生錯誤。
* 我們比較熟悉的Checked異常有
* Java.lang.ClassNotFoundException
* Java.lang.NoSuchMetodException
* java.io.IOException
* <p>
* RunTimeException 回滾:
* Runtime如除數是0和數組下標越界等,其產生頻繁,處理麻煩,若顯示申明或者捕獲將會對程序的可讀性和運行效率影響很大。所以由系統自動檢測並將它們交給缺省的異常處理程序。當然如果你有處理要求也可以顯示捕獲它們。
* 我們比較熟悉的RumtimeException類的子類有
* Java.lang.ArithmeticException
* Java.lang.ArrayStoreExcetpion
* Java.lang.ClassCastException
* Java.lang.IndexOutOfBoundsException
* Java.lang.NullPointerException
*
* @Transactional(rollbackFor=RuntimeException.class)
* 指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 這個是支持輸入的類名稱 詳細功能同上
* @Transactional(rollbackForClassName="RuntimeException")
* 指定多個異常類名稱:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
*/
String[] rollbackForClassName() default {};
/**
* 不會滾的異常可以在這設置 ,也可以自己實現異常 但是必須要繼承runtimeExceptional
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* 同上 這個是支持輸入的類名稱 不會滾的異常可以在這設置 ,也可以自己實現異常 但是必須要繼承runtimeExceptional
*/
String[] noRollbackForClassName() default {};
}
2.事務使用
(1). 一個方法中涉及兩次及以上重要插入,用事務來保證完整性 原子性
(2). 事務只能用在public 方法上 ,其他方法不起作用
(3). 可以放在ServiceImpl類上。全局實現事務
下面舉一個簡單的例子:
@Override
@Transactional(rollbackFor = Exception.class)
public int insert(DneWechat record) {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
//該方法是被第二次調用的方法 如果拋出異常 則全部回滾
//該方法可以不用寫@transectional 因爲事務是可以繼承的
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
throw new RuntimeException();
}
3.事務失效的幾種場景
一、try catch之後沒有拋出對應的異常,這樣是不會回滾的,錯誤例子如下:
@Override
@Transactional(rollbackFor = Exception.class)
public int insert(DneWechat record) {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
try {
int a = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
二、 private 方法 不會回滾,錯誤例子如下:
@Override
@Transactional
public int insert(DneWechat record) throws IOException {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
@Transactional(rollbackFor = Exception.class)
private void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
}
三、 檢查異常 例如ioexception 不會回滾,例子如下:
@Override
@Transactional
public int insert(DneWechat record) throws IOException {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
throw new IOException();
}
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
}
四、 被調用的方法有註解,Override 方法沒有註解,不會回滾,因爲註解注入是通過aop實現的,例子如下:
@Override
public int insert(DneWechat record) {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
throw new RuntimeException();
}
我等採石之人,當心懷大教堂之願景!
歡迎關注我的公衆號!!