什麼是事務
事務是一系列操作組成的工作單元,該工作單元具有不可分割性,一損俱損。滿足ACID(原子性,一致性,隔離性,持久性)
事務按分佈式劃分可以分爲本地事務,和分佈式事務
分別由JDBC事務和JTA事務與其對應。
Transaction其實在某些具體業務上,是相當實用的利器。但是我在工作之前對他的認識只是停留在概念的層面,現在想想還是很有必要好好總結一下的。
事務最經典的例子就是銀行轉賬問題,A用戶轉賬5000元給B用戶,如果在不發生任何意外的情況下,那麼是一點問題沒有的,但是如果這兩部操作中間出現了意外(例如發生了異常),很有可能這500元只轉出了,並沒有轉入。那麼這個問題的根本原因是兩個操作在代碼層面來看是相互獨立的,並不具備原子性導致的。Spring又是怎麼解決這個問題的呢?
再來通過代碼層面分析一下這個問題,轉出的時候,我們通過DataSource拿到一個Connection對象,當執行沒有異常的時候,直接提交事務,代碼並不知道還有轉入操作的存在。
所以spring針對這一點,如果在Service層的一個方法開啓了事務,那麼會關閉在這個方法中調用Dao方法自動提交事務的屬性,等到整個service執行後再做提交,具體的步驟如下:
- 獲取DataSource對象
- 通過DataSource對象獲取對應的Connection對象
- 關閉事務的自動提交機制,在Connection對象中
- 把Connection對象綁定到當前線程中
- 在Dao中通過取得當前線程的Connection然後執行操作
- 如果整個Service都ok則Commit,否則進行rollback
事務的隔離機制
數據庫的併發的問題,應運而生:例如說髒讀,虛讀,第一類丟失更新,第二類丟失更新。
解決的辦法就是通過不同的隔離機制,進行隔離:
- Read Uncommited
- Read Commited
- Repeatable Read
- Serializable
Oracle 默認使用Read Comited, Mysql默認使用 Repeatable Read。
隔離機制越高,性能越差。
事務的傳播規則
在一個事務方法中,調用了別的事務,應該按照什麼規則進行傳遞。
傳播規則一共分爲七種:
現在有這樣一種情況A方法調用了B方法。
- required:必須存在一個事務,如果有事務,則加入到該事務,如果沒有則新建。解讀:A如果有事務,B就用A的事務,如果A沒有事務,則B新建一個事務
- supports:如果有事務,則用。沒有則不用。解讀:A如果有事務,B就用A的,A如果沒有,B則不用事務。
- Mandatory:必須存在事務,當前如果有事務,則用。沒有則直接報異常。解讀:A如果有事務,B就用A的事務,如果沒有,則直接報錯。
- required_new: 不管當前是否存在事務,都會創建一個新的,這個在平常比較多。
- not_supports: 以非事務方式執行,如果當前存在事務,則將當前事務掛起 解讀:A有自己的事務,B不使用A的事務,B不參與A事務的管理。
- never:不支持事務,當前如果存在事務,則拋出異常。
- nested:寄生事務。如果內部事務進行回滾,不會影響到外部事務,如果外部事務回滾了,內部事務會被影響。
Spring對事務的支持
Spring的事務管理一定要在業務層上的
- PlatformTransactionManager 根據TransactionBefination提供的事務信息,進行配置。是多種事務管理器的基類。Hibernate使用的是HibernateTransactionManager,Mybatis/JDBC使用的是DataSourceTransactionManager。PlatformTransactionManager 一共擁有三個方法:
- getTransaction(TransactionDefination),在當前環境中取得一個事務,如果不存在,則新建。有點像是一種緩存機制
- commit:提交事務
- rollback:回滾事務
- TransactionDefination:封裝了事務隔離級別,超時時間等。
- TransactionStatus:封裝了事務具體運行的狀態,是否是新開的事務,是否已經提交事務
Xml方式進行配置:
下方是Spring官網給的例子
//業務接口:
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
<!--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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 首先將剛剛的業務類注入進容器中 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 配置數據庫連接池,因爲連接池會作爲屬性注入到TransactionManager中 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- 配置PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置transaction 具體的一些配置 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- 如果是方法名以get作爲開頭的,說明是查詢方法,那麼配置只讀操作-->
<tx:method name="get*" read-only="true"/>
<!-- 其他的增和改操作,就是用默認的即可-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 使用Aop把transactionManager作爲對業務邏輯的增強操作 -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- other <bean/> definitions here -->
</beans>
在<tx:advice/>
中的一些詳細配置,官網也給出了相應的一些說明,如下圖:
Attribute | Required? | Default | Description |
---|---|---|---|
name | Yes | 事務管理的方法名稱,並且支持通配符,例如 get* , handle* , on*Event , 等等). |
|
propagation | No | REQUIRED | Transaction propagation behavior. |
isolation | No | DEFAULT | 事務的隔離級別,當傳遞規則爲 REQUIRED or REQUIRES_NEW 纔可以設置,當是默認值default的時候,指的是使用數據庫隔離級別。其他四種都是Spring 通過代碼模擬出來的 |
timeout | No | -1 | 事務超時時間 (seconds),當傳遞規則爲 REQUIRED or REQUIRES_NEW 纔可以設置,默認值-1代表使用數據庫本身的值,一般情況下,不需要進行修改。 |
read-only | No | false | 一般對查詢進行設置只讀,可以提升事務的效率。只應用於 REQUIRED or REQUIRES_NEW . |
rollback-for | No | java.lang.RunTimeException | 遇到什麼異常需要做事務的回滾,例如,com.foo.MyBusinessException,ServletException.
|
no-rollback-for | No | 遇到什麼異常不做回滾,com.foo.MyBusinessException,ServletException.
|
Java註解方式
首先我們需要在配置類上,開啓對事務的支持,使用@EnableTransactionManagement
官網的例子:
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
@Transactional 註解可以用來放在實現類上,也可以放在接口上,最好是放在實現類上。如果加在了實現類上,那麼也就是說對這個類裏的所有方法都支持開啓事務。如果有哪個類需要一些定製化的屬性,只需要在方法上再加上這個註解並且貼上定製的屬性即可。
@Transactional可以使用的屬性:
Property | Type | Description |
---|---|---|
value | String |
|
propagation |
enum : Propagation
|
|
isolation |
enum : Isolation
|
隔離級別的設置,用於傳遞屬性爲 REQUIRED or REQUIRES_NEW . |
timeout |
int (in seconds of granularity) |
事務超時時間用於傳遞屬性爲REQUIRES_NEW . |
readOnly | boolean |
是否爲只讀. 用於傳遞屬性爲 REQUIRES_NEW . |
rollbackFor | Array of Class objects, which must be derived from Throwable.
|
|
rollbackForClassName | Array of class names. The classes must be derived from Throwable.
|
哪些異常類處罰會導致回滾(使用異常類名) |
noRollbackFor | Array of Class objects, which must be derived from Throwable.
|
哪些異常類處罰不會導致回滾(使用異常類) |
noRollbackForClassName | Array of String class names, which must be derived from Throwable.
|
哪些異常類處罰不會導致回滾(使用異常類名) |
可以看出來這些屬性與xml配置的大同小異。