前言
我們都知道Spring給我們提供了很多抽象,比如我們在運算元據庫的過程中,它為我們提供了事物方面的抽象,讓我們可以非常方便的以事物方式運算元據庫。不管你用JDBC、Mybatis、Hibernate等任何一種方式運算元據庫,也不管你使用DataSource還是J他的事物,Spring事物抽象管理都能很好的把他統一在一起。接下來看一下事物的抽象核心介面
Spring事務抽象
PlatformTransactionManager是事物管理器介面
//事務管理器介面有以下幾個介面,獲取事物資訊,提交和回滾
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
常見的事物管理器有以下幾種
- DataSourceTransactionManager
- HibernateTransactionManager
- JtaTransactionManager
這些管理器都實現了PlatformTransactionManager中的三個介面,實現邏輯略有差別,但是對使用者來講區別不大
定義事物的一些引數:
一些事物的引數在TransactionDefinition.java中,詳情如下:
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
//預設隔離級別,和資料庫的隔離級別一致
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
//預設不超時
int TIMEOUT_DEFAULT = -1;
}
下面兩張圖對這些引數進行了說明:
7種事務傳播特性:
四種事務隔離級別:
在看事務隔離級別前需要先了解下什麼是髒讀、不可重複讀、幻讀
髒讀: 髒讀就是一個事物未提交的資料,被另外一個事物讀到了,顯然這種情況不可接受
不可重複讀: 不可重複讀是指在一個事務內,多次讀同一資料,前後讀取的結果不一致。
幻讀: 事務A對錶中的一個資料進行了修改,這種修改涉及到表中的全部資料行。同時事務B也修改了這個表中的資料,這種修改是向表中插入一行新資料。那麼就會發生操作事務A的使用者發現表中還存在沒有修改的資料行,就好像發生了幻覺一樣
知道了以上幾個概念,我們來看看隔離級別:
這裡我們可以看到,Spring並不是提供了所有的事物管理的實現,而是提供了標準的事物管理器的操作介面PlatformTransactionManager, 並且規範了其行為,具體的事物實現由各個平臺自行實現。這就是Spring的事物抽象。
Spring之程式設計式事物
Spring提供了TransactionTemplate工具類可以很方便的使用程式設計式事務。預設情況下TransactionTemplate使用的是DataSourceTransactionManager。
在Spring上下文中,我們不配置TransactionTemplate這個bean,也能獲取到TransactionTemplate。比如下面的例子。
@Service
public class UserInfoService {
@Resource
private UserInfoDAO userInfoDAO;
@Autowired
private TransactionTemplate transactionTemplate;
public void updateUser1(){
transactionTemplate.execute(transactionStatus -> {
userInfoDAO.updateUserName(1,"zhangsanfeng");
transactionTemplate.execute(transactionStatus2 -> {
userInfoDAO.updateUserName(2,"lisi");
return null;
});
return null;
});
}
}
由於Spring預設的事物傳播特性是PROPAGATION_REQUIRED,我們來做一下驗證,看是不是這樣
上面兩幅圖可以看出,TransactionStatus中的newTransaction屬性,第一個是true,第二個是false,正好符合PROPAGATION_REQUIRED所描述的情況。其他的傳播特性可以自己去驗證。
宣告式事物
除了程式設計式事物外,Spring還為我們提供了宣告式事物。使用@Transactional註解。
@Transactional 可以作用於介面、介面方法、類以及類方法上。當作用於類上時,該類的所有 public 方法將都具有該型別的事務屬性,同時,我們也可以在方法級別使用該註解來覆蓋類級別的定義。
雖然 @Transactional 註解可以作用於介面、介面方法、類以及類方法上,但是 Spring 建議不要在介面或者介面方法上使用該註解,因為這隻有在使用基於介面的代理時它才會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 註解,這將被忽略,也不會丟擲任何異常。
@Transactional的rollbackFor屬性可以設定一個 Throwable 的陣列,用來表明如果方法丟擲這些異常,則進行事務回滾。預設情況下如果不配置rollbackFor屬性,那麼事務只會在遇到RuntimeException的時候才會回滾。
下面的程式碼事物就不會生效:
@Transactional
public void updateUser2() throws Exception {
int r1 = userInfoDAO.updateUserName(1,"wanger");
int r2 = userInfoDAO.updateUserName(2,"mawu");
if (r2==1){
throw new Exception();
}
}
如果我們把丟擲的異常改成RuntimeException,這時候事物就會生效了。或者指定異常讓事物生效,比如 @Transactional(rollbackFor = Exception.class),這樣碰到所有的異常事物都會生效了。
為什麼加了@Transactional註解事物就生效了?
這是因為Spring容器會為加了這個註解的物件生成一個代理物件,實際呼叫的時候,實際上是呼叫的代理物件。 代理物件的實現了AOP的增強,實現了事物的實現。
通過註解怎麼實現指定的傳播特性和隔離級別的?
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
程式碼中可以看出,我們可以指定隔離級別和傳播特性,在Spring為我們生成代理類的時候,會讀取這些屬性,體現在增強邏輯中。
事物失效的8種情況及解決辦法
資料庫引擎不支援事務
這裡以 MySQL 為例,其 MyISAM 引擎是不支援事務操作的,InnoDB 才是支援事務的引擎,一般要支援事務都會使用 InnoDB,這時候選擇支援事物的資料庫即可(好像是廢話,哈哈哈)
沒有被 Spring 管理
這個好像沒什麼可說的,脫離了Spring的管理,還談什麼Spring事物管理。
方法不是 public 的
@Transactional 只能用於 public 的方法上,否則事務不會失效,如果要用在非 public 方法上,可以開啟 AspectJ 代理模式。
資料來源沒有配置事務管理器
相當於沒開啟事務管理,如果不是Springboot情況需要進行如下操作。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
如果是SpringBoot,在啟動類上直接加上註解@EnableTransactionManagement即可。
傳播特性配錯了
傳播特性配置成,Propagation.NOT_SUPPORTED或者Propagation.NOT_SUPPORTED,改成支援事物的傳播特性即可。
異常型別錯誤
因為預設的異常型別是執行時異常,如果丟擲了其他異常就不生效。
解決方式:
1、將異常改成執行時異常
2、指定異常進行事物回滾,如:@Transactional(rollbackFor = Exception.class)
異常被吃掉了
如果你程式碼這麼寫,事物不生效:
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
int r1 = userInfoDAO.updateUserName(1,"3");
int r2 = userInfoDAO.updateUserName(2,"4");
if (r2==1){
throw new RuntimeException();
}
try {
}catch (Exception e){
}
}
解決辦法: 必須要丟擲異常,否則Spring事務管理,不會走到回滾邏輯
類內部呼叫
@Service
public class UserInfoService {
public void justUpdate(){
updateUser2();
}
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
}
}
上述程式碼不生效,因為內部呼叫不會涉及到代理類的呼叫,更不會有AOP的增強,因此不會生效。
解決辦法:
1、自注入
@Service
public class UserInfoService {
@Autowired
private UserInfoService userInfoService;
public void justUpdate(){
userInfoService.updateUser2();
}
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
}
}
2、Spring上下文
@Service
public class UserInfoService {
ApplicationContext applicationContext;
public void justUpdate(){
UserInfoService userInfoService = (UserInfoService) applicationContext.getBean("userInfoService");
userInfoService.updateUser2();
}
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
}
}
3、獲取他的代理類,直接呼叫代理類
@Service
public class UserInfoService {
public void justUpdate(){
((UserInfoService) AopContext.currentProxy()).updateUser2();
}
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
}
}
----------------------------END---------------------------
更多Spring相關知識,請關注我,各平臺都是同一個ID