什么是事務(wù)
事務(wù)是一系列操作組成的工作單元,該工作單元具有不可分割性,一損俱損。滿足ACID(原子性,一致性,隔離性,持久性)
事務(wù)按分布式劃分可以分為本地事務(wù),和分布式事務(wù)
分別由JDBC事務(wù)和JTA事務(wù)與其對(duì)應(yīng)。
Transaction其實(shí)在某些具體業(yè)務(wù)上,是相當(dāng)實(shí)用的利器。但是我在工作之前對(duì)他的認(rèn)識(shí)只是停留在概念的層面,現(xiàn)在想想還是很有必要好好總結(jié)一下的。
事務(wù)最經(jīng)典的例子就是銀行轉(zhuǎn)賬問題,A用戶轉(zhuǎn)賬5000元給B用戶,如果在不發(fā)生任何意外的情況下,那么是一點(diǎn)問題沒有的,但是如果這兩部操作中間出現(xiàn)了意外(例如發(fā)生了異常),很有可能這500元只轉(zhuǎn)出了,并沒有轉(zhuǎn)入。那么這個(gè)問題的根本原因是兩個(gè)操作在代碼層面來看是相互獨(dú)立的,并不具備原子性導(dǎo)致的。Spring又是怎么解決這個(gè)問題的呢?
再來通過代碼層面分析一下這個(gè)問題,轉(zhuǎn)出的時(shí)候,我們通過DataSource拿到一個(gè)Connection對(duì)象,當(dāng)執(zhí)行沒有異常的時(shí)候,直接提交事務(wù),代碼并不知道還有轉(zhuǎn)入操作的存在。
所以spring針對(duì)這一點(diǎn),如果在Service層的一個(gè)方法開啟了事務(wù),那么會(huì)關(guān)閉在這個(gè)方法中調(diào)用Dao方法自動(dòng)提交事務(wù)的屬性,等到整個(gè)service執(zhí)行后再做提交,具體的步驟如下:
- 獲取DataSource對(duì)象
- 通過DataSource對(duì)象獲取對(duì)應(yīng)的Connection對(duì)象
- 關(guān)閉事務(wù)的自動(dòng)提交機(jī)制,在Connection對(duì)象中
- 把Connection對(duì)象綁定到當(dāng)前線程中
- 在Dao中通過取得當(dāng)前線程的Connection然后執(zhí)行操作
- 如果整個(gè)Service都o(jì)k則Commit,否則進(jìn)行rollback
事務(wù)的隔離機(jī)制
數(shù)據(jù)庫的并發(fā)的問題,應(yīng)運(yùn)而生:例如說臟讀,虛讀,第一類丟失更新,第二類丟失更新。
解決的辦法就是通過不同的隔離機(jī)制,進(jìn)行隔離:
- Read Uncommited
- Read Commited
- Repeatable Read
- Serializable
Oracle 默認(rèn)使用Read Comited, Mysql默認(rèn)使用 Repeatable Read。
隔離機(jī)制越高,性能越差。
事務(wù)的傳播規(guī)則
在一個(gè)事務(wù)方法中,調(diào)用了別的事務(wù),應(yīng)該按照什么規(guī)則進(jìn)行傳遞。
傳播規(guī)則一共分為七種:
現(xiàn)在有這樣一種情況A方法調(diào)用了B方法。
- required:必須存在一個(gè)事務(wù),如果有事務(wù),則加入到該事務(wù),如果沒有則新建。解讀:A如果有事務(wù),B就用A的事務(wù),如果A沒有事務(wù),則B新建一個(gè)事務(wù)
- supports:如果有事務(wù),則用。沒有則不用。解讀:A如果有事務(wù),B就用A的,A如果沒有,B則不用事務(wù)。
- Mandatory:必須存在事務(wù),當(dāng)前如果有事務(wù),則用。沒有則直接報(bào)異常。解讀:A如果有事務(wù),B就用A的事務(wù),如果沒有,則直接報(bào)錯(cuò)。
- required_new: 不管當(dāng)前是否存在事務(wù),都會(huì)創(chuàng)建一個(gè)新的,這個(gè)在平常比較多。
- not_supports: 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則將當(dāng)前事務(wù)掛起 解讀:A有自己的事務(wù),B不使用A的事務(wù),B不參與A事務(wù)的管理。
- never:不支持事務(wù),當(dāng)前如果存在事務(wù),則拋出異常。
- nested:寄生事務(wù)。如果內(nèi)部事務(wù)進(jìn)行回滾,不會(huì)影響到外部事務(wù),如果外部事務(wù)回滾了,內(nèi)部事務(wù)會(huì)被影響。
Spring對(duì)事務(wù)的支持
Spring的事務(wù)管理一定要在業(yè)務(wù)層上的
- PlatformTransactionManager 根據(jù)TransactionBefination提供的事務(wù)信息,進(jìn)行配置。是多種事務(wù)管理器的基類。Hibernate使用的是HibernateTransactionManager,Mybatis/JDBC使用的是DataSourceTransactionManager。PlatformTransactionManager 一共擁有三個(gè)方法:
- getTransaction(TransactionDefination),在當(dāng)前環(huán)境中取得一個(gè)事務(wù),如果不存在,則新建。有點(diǎn)像是一種緩存機(jī)制
- commit:提交事務(wù)
- rollback:回滾事務(wù)
- TransactionDefination:封裝了事務(wù)隔離級(jí)別,超時(shí)時(shí)間等。
- TransactionStatus:封裝了事務(wù)具體運(yùn)行的狀態(tài),是否是新開的事務(wù),是否已經(jīng)提交事務(wù)
Xml方式進(jìn)行配置:
下方是Spring官網(wǎng)給的例子
//業(yè)務(wù)接口:
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
<!--xml文件關(guān)于事務(wù)的配置-->
<?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">
<!-- 首先將剛剛的業(yè)務(wù)類注入進(jìn)容器中 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 配置數(shù)據(jù)庫連接池,因?yàn)檫B接池會(huì)作為屬性注入到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"/>
<!-- 其他的增和改操作,就是用默認(rèn)的即可-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 使用Aop把transactionManager作為對(duì)業(yè)務(wù)邏輯的增強(qiáng)操作 -->
<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/>中的一些詳細(xì)配置,官網(wǎng)也給出了相應(yīng)的一些說明,如下圖:
| Attribute | Required? | Default | Description |
|---|---|---|---|
| name | Yes | 事務(wù)管理的方法名稱,并且支持通配符,例如 get*, handle*, on*Event, 等等). |
|
| propagation | No | REQUIRED | Transaction propagation behavior. |
| isolation | No | DEFAULT | 事務(wù)的隔離級(jí)別,當(dāng)傳遞規(guī)則為 REQUIRED or REQUIRES_NEW才可以設(shè)置,當(dāng)是默認(rèn)值default的時(shí)候,指的是使用數(shù)據(jù)庫隔離級(jí)別。其他四種都是Spring 通過代碼模擬出來的 |
| timeout | No | -1 | 事務(wù)超時(shí)時(shí)間 (seconds),當(dāng)傳遞規(guī)則為 REQUIRED or REQUIRES_NEW才可以設(shè)置,默認(rèn)值-1代表使用數(shù)據(jù)庫本身的值,一般情況下,不需要進(jìn)行修改。 |
| read-only | No | false | 一般對(duì)查詢進(jìn)行設(shè)置只讀,可以提升事務(wù)的效率。只應(yīng)用于 REQUIRED or REQUIRES_NEW. |
| rollback-for | No | java.lang.RunTimeException | 遇到什么異常需要做事務(wù)的回滾,例如,com.foo.MyBusinessException,ServletException.
|
| no-rollback-for | No | 遇到什么異常不做回滾,com.foo.MyBusinessException,ServletException.
|
Java注解方式
首先我們需要在配置類上,開啟對(duì)事務(wù)的支持,使用@EnableTransactionManagement
官網(wǎng)的例子:
@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 注解可以用來放在實(shí)現(xiàn)類上,也可以放在接口上,最好是放在實(shí)現(xiàn)類上。如果加在了實(shí)現(xiàn)類上,那么也就是說對(duì)這個(gè)類里的所有方法都支持開啟事務(wù)。如果有哪個(gè)類需要一些定制化的屬性,只需要在方法上再加上這個(gè)注解并且貼上定制的屬性即可。
@Transactional可以使用的屬性:
| Property | Type | Description |
|---|---|---|
| value | String |
|
| propagation |
enum: Propagation
|
|
| isolation |
enum: Isolation
|
隔離級(jí)別的設(shè)置,用于傳遞屬性為 REQUIRED or REQUIRES_NEW. |
| timeout |
int (in seconds of granularity) |
事務(wù)超時(shí)時(shí)間用于傳遞屬性為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.
|
哪些異常類處罰會(huì)導(dǎo)致回滾(使用異常類名) |
| noRollbackFor | Array of Class objects, which must be derived from Throwable.
|
哪些異常類處罰不會(huì)導(dǎo)致回滾(使用異常類) |
| noRollbackForClassName | Array of String class names, which must be derived from Throwable.
|
哪些異常類處罰不會(huì)導(dǎo)致回滾(使用異常類名) |
可以看出來這些屬性與xml配置的大同小異。