事務支持
什麼是事務?
在一個業務流程中,需要多條DML(insert、delete、update)語句聯合才能完成。這些語句必須同時成功或者同時失敗。這樣才能保證數據安全。
多條DML同時成功或者同時失敗,叫做事務。
事務處理的四個過程
- 開啓事務
- 執行業務代碼
- 提交事務(沒出現異常,提交成功。commit transaction)
- 回滾事務(出現異常。執行回滾事務. rollback transaction)
事務的四個特性(ACID)
- A原子性:事務是最小的工作單元,不可分
- C一致性:事務要麼同時成功,要麼同時失敗
- I隔離性:事務與事務之間保證和互不干擾
- D持久性:持久性是事務結束的標誌。
spring對事務的支持
spring實現事務的2種方式:
- 編程式事務:通過編寫代碼的方式來實現事務管理
- 聲明式事務:基於註解方式和基於xml方式(推薦使用)
spring事務管理api
spring對事務的管理是基於aop實現的。所以spring專門針對事務開發了一套api,其核心接口如下:PlatformTransactionManager 接口。
聲明式事務基於註解方式實現
需要配置xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" >
<!-- 組件掃描-->
<context:component-scan base-package="com.ali" />
<!-- 配置數據源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/bank"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
</bean>
<!-- 配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 開啓事務註解驅動器,開啓事務註解,需要加上tx的命名空間-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
可以在類和方法上加@Transactional 開啓事務
- 加在類上表示這個類上的所有方法都開啓事務
- 加在方法方法上表示只有這個方法開啓事務
事務的傳播行為
什麼是事務的傳播行為?
在service種有a()和b()2個方法,a()上有事務,b()上也有事務,當a()在執行過程中調用了b(),事務是如何傳遞的?是合併到一個事務?還是開啓一個新事務?這就是事務的傳播行為。
一共有7種傳播行為:
- REQUIRD:支持當前事務,如果不存在就新建一個事務(默認)【沒有就新建,有就加入】
- SUPPORTS:支持當前事務,如果當前沒有事務,就以非事務方式執行【有就加入,沒有就不管了】
- MANDATORY:必須運行在一個事務中,如果當前沒有事務發生,將拋出異常【有就加入,沒有就拋異常】
- REQUIRES_NEW:開啓一個新事務,如果一個事務已經存在,則將這個存在的事務掛起【不管有沒有。直接開啓一個新事務。新事務和舊事務不存在嵌套關係,舊事務被掛起了】
- NOT_SUPPORTED:以非事務方式運行。如果有事務。則掛起當前事務【不支持事務,存在就掛起】
- NEVER:以非事務方式運行。如果有事務。則拋異常【不支持事務,存在就拋異常】
- NESTED:如果當前有一個事務在進行中,則該方法應當運行在一個嵌套事務中。被嵌套的事務可以獨立於外層事務進行提交或回滾。如果外層事務不存在。行為就像REQUIRD一樣【有事務的話,就在這個事務裏嵌套一個完全獨立的事務,嵌套的事務可以獨立的提交和回滾。沒有事務就和REQUIRD一樣】
在代碼中設置事務的傳播行為:
@Transactional(propagation = Propagation.MANDATORY)
事務隔離級別
數據庫中讀取數據存在三大問題:
- 髒讀:讀取到沒有提交到數據庫的數據
- 不可重複讀:在同一個事務中,第一次和第二次讀取的數據不一樣
- 幻讀:讀到的數據是假的()
事務的隔離級別有4個:
- 讀未提交READ_UNCOMMITTED: 存在髒讀、不可重複讀、幻讀問題
- 讀提交READ_COMMITTED:事務提交之後才讀到。存在不可重複讀、幻讀問題
- 可重複讀REPEATABLE_READ:解決不可重複讀的問題,存在幻讀問題
- 序列化SERIALIZABLE:解決幻讀問題,事務排隊執行。不支持併發。
MySQL默認可重複讀,Oracle默認讀提交
僅在讀的事務中設置隔離級別就行,寫的事務沒必要設置
代碼中設置事務的隔離級別:
@Transactional(isolation = Isolation.DEFAULT)
事務超時
@Transactional(timeout = 10)
以上代碼設置事務超時時間為10s
表示超過10s,如果事務中所有的DML語句還沒有執行完畢的話,最終結果會回滾。
默認值-1,表示沒有時間限制。
事務的超時時間值得是哪段時間?
在當前事務中,最後一條DML語句執行之前的時間,如果最後一條DML語句後面有很多業務邏輯,這些業務代碼執行的時間不被計入超時時間。
只讀事務
@Transactional(readOnly = true)
將當前事務設為只讀事務,在該事務中只允許執行select 語句。
該特性的作用是:啓動spring的優化策略。提高select語句的執行效率。
設置哪些異常回滾事務
@Transactional(rollbackFor = RuntimeException.class)
表示發生RuntimeException異常或該異常的子類異常才回滾
設置哪些異常不回滾事務
@Transactional(noRollbackFor = NullPointerException.class)
表示發生NullPointerException異常或該異常的子類不回滾,其他異常才回滾
事務的全註解式開發
編寫配置類
@Configuration // 代替xml配置文件
@ComponentScan("com.ali") // 掃描com.ali包下的所有類
@EnableTransactionManagement // 開啓事務管理
public class Spring6Config {
// @Bean註解用於將方法的返回值註冊為Spring容器中的一個Bean
@Bean(name = "druidDataSource")
public DruidDataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/spring6?useSSL=false&serverTimezone=UTC");
druidDataSource.setUsername("root");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setPassword("123456");
return druidDataSource;
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Bean(name = "jdbcTemplate")
// 該方法的參數DataSource dataSource會自動從Spring容器中找到類型為DataSource的Bean並注入
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
使用時和其他方式一樣。