編程式事務與聲明式事務的理解
補充:什么是事務?
事務是一個重要概念,尤其在數據庫管理系統中。事務是指一組操作。,這些操作要么全部成功執行,要么全部不執行,確保數據的一致性和完整性
編程式事務
編程式事務是指手動編寫程序來管理事務,即通過編寫代碼的方式直接控制事務的提交和回滾。在 Java 中,通常使用事務管理器(如 Spring 中的 `PlatformTransactionManager`)來實現編程式事務。
編程式事務的主要優點是靈活性高,可以按照自己的需求來控制事務的粒度、模式等等。但是,編寫大量的事務控制代碼容易出現問題,對代碼的可讀性和可維護性有一定影響 。
聲明式事務(必須要有聲明式事務的框架----------sprint-tx)
聲明式事務是指使用注解或 XML 配置的方式來控制事務的提交和回滾。
開發者只需要添加配置即可, 具體事務的實現由第三方框架實現,避免我們直接進行事務操作!
使用聲明式事務可以將事務的控制和業務邏輯分離開來,提高代碼的可讀性和可維護性。
程序員只需要在配置文件即可(注解/xml)
指定那些方法需要添加事務以及事務的屬性即可
區別:
- 編程式事務需要手動編寫代碼來管理事務
- 而聲明式事務可以通過配置文件或注解來控制事務。
事務管理器
spring的事務管理會幫我們提供一個增強類(事務增強)有三個方法(開啟事務(前置),提交事務(返回),事務回滾(異常));
但是持久層的框架有很多(不同框架事務的操作不同);所以提供了一個接口(事務管理器-----------具體提供事務方法的);在事務增強中通過? 接口.方法()--------實現對應方法
spring提供了多種實現類(對應不同的數據庫):
DataSoureceTransactionManager重寫三個方法(jdbc,jdbcTampalte,myBatis框架)
我們使用的是那種數據庫,就將對應的實現配置到ioc容器中;
然后在事務增強類上使用@Autowired(將實現類注入到增強類組件)
事務管理器
1. Spring聲明式事務對應依賴
??? - spring-tx: 包含聲明式事務實現的基本規范(事務管理器規范接口和事務增強等等)
??? - spring-jdbc: 包含DataSource方式事務管理器實現類DataSourceTransactionManager
??? - spring-orm: 包含其他持久層框架的事務管理器實現類例如:Hibernate/Jpa等
2. Spring聲明式事務對應事務管理器接口
???
我們現在要使用的事務管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,將來整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事務實現!
??? DataSourceTransactionManager類中的主要方法:
??? - doBegin():開啟事務
??? - doSuspend():掛起事務
??? - doResume():恢復掛起的事務
??? - doCommit():提交事務
??? - doRollback():回滾事務
基本實現
什么時候需要開啟事務:
開啟事務通常用于確保一組操作的原子性,一致性,隔離性,持久性
①多個操作需要原子性
當一組操作需要全部成功或者失敗時
②數據一致性要求高
當多個操作需要保持數據的一致性時,事務可以確保在操作過程中數據不會處于不一致的狀態
③并發控制
當多個用戶或進程同時訪問或修改同一數據時,事務可以通過 隔離級別來控制并訪問
④錯誤恢復
當操作過程中可能發生錯誤時,事務可以確保在錯誤發生時回滾到操作前的狀態,避免數據破損
⑥審計和日志記錄
步驟:
1 選擇對應的事務管理器實現加入到ioc容器
spring聲明式事務給我們提供了各種管理器的實現
需要那種,就加入到ioc容器即可(在配置類中做此操作-------------類似于將第三方組件加入到ioc)
2 只需要使用注解指定那些方法需要添加事務即可---------------聲明式事務
3要在配置類開啟事務注解的支持(@EnableTransactionManagement)
???? * 給方法添加事務的操作
???? *??????? 1 選定我們需要的事務管理器的實現類,在配置類中創建該類的對象,使用注解的方法將該類添加到ioc
???? *???????????????????????? 注意:該組件需要引入連接池的組件
???? *??????? 2 在配置類上加上 @EnableTransactionManagement 表示開啟事務注解
???? *??????? 3 在我們想要添加事務的方法上加上 @Transactional那么該方法就有事務了
???? *添加事務:
???? *????? @Transactional
???? *????? 位置:方法/類上
???? *????? 方法:當前方法有事務
???? *????? 類上:類下所有方法都有事務
代碼舉例:
配置類
@Configuration
@ComponentScan("com.atguigu")
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement//開啟事務注解的支持
public class JavaConfig {@Value("${atguigu.url}")private String url;@Value("${atguigu.driver}")private String driver;@Value("${atguigu.username}")private String username;@Value("${atguigu.password}")private String password;@Beanpublic DruidDataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(url);dataSource.setDriverClassName(driver);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DruidDataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic TransactionManager transactionManager(DruidDataSource dataSource){/*內部要進行事務操作,是基于連接池的所以要將連接池給它*/DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}
}
Service類(要添加事務的方法在其中)
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;/***添加事務:* @Transactional* 位置:方法/類上* 方法:當前方法有事務* 類上:類下所有方法都有事務*/@Transactionalpublic void changeInfo(){studentDao.updateAgeById(88,1);int i=1/0;System.out.println("-----------");studentDao.updateNameById("test2",1);}
}
測試代碼:
@SpringJUnitConfig(JavaConfig.class)//指定那個是配置類
public class SpringTxTest {
??? @Autowired
??? private StudentService studentService;
??? @Test
??? public void test(){
??????? studentService.changeInfo();
??? }
}
事務的幾個屬性設置?
只讀模式
???? * 只讀模式
???? *?????? 只讀模式可以提升查詢事務的效率!推薦事務中只有查詢代碼是,使用只讀模式
???? *?????? 默認: boolean readOnly() default false;
???? *?????? 解釋: 一般情況下,都是通過類添加注解添加事務!
???? *???????????? 類下所有方法全部都有事務!
???? *???????????? 查詢方法可以通過再次添加注解,設置只讀模式提高效率!
?
1. 只讀介紹(效率會提高,但是不允許做修改)
??? 對一個查詢操作來說,如果我們把它設置成只讀,就能夠明確告訴數據庫,這個操作不涉及寫操作。這樣數據庫就能夠針對查詢操作來進行優化。
2?? 設置方式
// readOnly = true把當前事務設置為只讀 默認是false!
@Transactional(readOnly = true)
事務超時時間設置
1. 需求
??? 事務在執行過程中,有可能因為遇到某些問題,導致程序卡住,從而長時間占用數據庫資源。而長時間占用資源,大概率是因為程序運行出現了問題(可能是Java程序或MySQL數據庫或網絡連接等等)。
??? 此時這個很可能出問題的程序應該被回滾,撤銷它已做的操作,事務結束,把資源讓出來,讓其他正常程序可以執行。
??? 概括來說就是一句話:超時回滾,釋放資源。
2? 設置:
??? /**
???? * timeout設置事務超時時間,單位秒! 默認: -1 永不超時,不限制事務時間!
???? */
??? @Transactional(readOnly = false,timeout = 3)
?
???? * 超時時間
???? *??????? 默認:永不超時? -1
???? *??????? 設置 timeout = 時間? (秒數)
???? *??????? 超過時間就會回滾事務和釋放異常
???? *??? 如果類上設置事務屬性,設置了超時時間,而方法上有設置了事務屬性,沒有設置超時時間,那么時間限制會不會生效
???? *??? 不會;因為:方法上的注解會將類上的注解覆蓋
事務異常指定問題
???? * 指定異常回滾和指定異常不回滾
???? *???????? 默認情況下,指定發生運行時異常事務才會回滾!
???? *???????? 我們可以指定Exception異常來控制所有異常都回滾!
???? *???????????? @Transactional(rollbackFor = Exception.class)
???? *???????????? noRollbackFor=回滾異常范圍內,控制某個異常不回滾。
代碼舉例
在service類中
? ??@Transactional(rollbackFor = Exception.class,noRollbackFor = FileAlreadyExistsException.class)
??? public void changeInfo(){
??????? studentDao.updateAgeById(88,1);
??????? int i=1/0;
??????? System.out.println("-----------");
??????? studentDao.updateNameById("test2",1);
??? }
事務隔離級別
??? 數據庫事務的隔離級別是指在多個事務并發執行時,數據庫系統為了保證數據一致性所遵循的規定。常見的隔離級別包括:
??? 1. 讀未提交(Read Uncommitted):事務可以讀取未被提交的數據,容易產生臟讀、不可重復讀和幻讀等問題。實現簡單但不太安全,一般不用。
??? 2. 讀已提交(Read Committed):事務只能讀取已經提交的數據,可以避免臟讀問題,但可能引發不可重復讀和幻讀。
??? 3. 可重復讀(Repeatable Read):在一個事務中,相同的查詢將返回相同的結果集,不管其他事務對數據做了什么修改。可以避免臟讀和不可重復讀,但仍有幻讀的問題。------------------------------默認是可重復讀(mysql中)
??? 4. 串行化(Serializable):最高的隔離級別,完全禁止了并發,只允許一個事務執行完畢之后才能執行另一個事務。可以避免以上所有問題,但效率較低,不適用于高并發場景。
??? 不同的隔離級別適用于不同的場景,需要根據實際業務需求進行選擇和調整。
?* 隔離級別設置
???? *???????? 推薦設置第二個隔離級別!
???? *???????? isolation = Isolation.READ_COMMITTED(讀已提交)
在service類中
??? @Transactional(readOnly = true,isolation = Isolation.READ_COMMITTED)
??? public void getStudentInfo(){
??????? //查詢沒有必要添加事務
??????? //獲取學生信息 查詢數據庫 不修改
??? }
?事務傳播行為
在執行業務方法1時(事務1)要執行業務方法2(事務2)
那么:方法2的事務是否會加入方法1的事務呢?
這取決于事務傳播的指定行為:事務之間的調用如何影響子事務
事務傳播行為屬性設置到子事務上
代碼舉例:
@Transactional
public void MethodA(){
??? // ...
??? MethodB();
??? // ...
}
//在被調用的子方法中設置傳播行為,代表如何處理調用的事務! 是加入,還是新事務等!
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodB(){
??? // ...
}
1. propagation屬性
??? @Transactional 注解通過 propagation 屬性設置事務的傳播行為。它的默認值是:
Propagation propagation() default Propagation.REQUIRED;
??? propagation 屬性的可選值由 org.springframework.transaction.annotation.Propagation 枚舉類提供:
??
* 聲明兩個獨立修改數據庫的事務業務方法
???? * propagation = Propagation.REQUIRED 父方法有事務,我們就加入到父方法的事務!
???? *??????????????????? 最終是同一個事務!(推薦使用默認值)
???? * propagation = Propagation.REQUIRES_NEW?
???? *??????????????????? 不管父方法中是否有事務,我都是獨立的事務!
???? *??????????????????? 兩個事務或者三個事務!
???? */
代碼舉例
在TopService中(將兩個事務合成了一個事務)
??? @Autowired
??? private StudentService studentService;
??? @Transactional
??? public void? topService(){
??????? studentService.changeAge();
??????? studentService.changeName();
??? }
在SudentService中
??? @Transactional(propagation = Propagation.REQUIRED)
??? public void changeAge(){
??????? studentDao.updateAgeById(998,1);
??? }
??? @Transactional(propagation = Propagation.REQUIRES_NEW)
??? public void changeName(){-?
??????? studentDao.updateNameById("二狗子",1);
??? }
**注意:**
? 在同一個類中,對于@Transactional注解的方法調用,事務傳播行為不會生效。這是因為Spring框架中使用代理模式實現了事務機制,在同一個類中的方法調用并不經過代理,而是通過對象的方法調用,因此@Transactional注解的設置不會被代理捕獲,也就不會產生任何事務傳播行為的效果。