資料庫--事務
個人分類: Java
首先,什麼是事務?
事務就是業務上的一個邏輯單元,它能夠保證其中對資料所有的操作,要麼成功,要麼失敗。
其次,事務的特性有哪些?
1.原子性。
例如,轉賬,A賬戶減少,B賬戶增加。雖然是兩條 DML語句,但是被當做是一個整體,一次事務。兩條語句只能同時成功或者同時失敗。
2.一致性。
賬戶A和B,要麼都是轉賬前的狀態,要麼都是轉賬後的狀態。(不能A賬戶的錢減少了但是B賬戶的錢沒有增加)。
3.隔離性。
雖然在某個時間段很多人都在轉賬,但是每個人的轉賬都是在一個自己的事務中,彼此不會影響。
4.永續性。
事務提交成功後,資料修改永遠生效。
在考慮事務的隔離級別之前,需要認識到如果不考慮事務的隔離性,會發生的異常情況:
1.髒讀
一個事務讀取了另外一個事務未提交的資料。(會對系統的併發處理帶來很大的隱患)
2.不可重複讀
在同一個事務內,多次讀同一個資料時,發現該資料已經 被另一個已經提交的事務修改。(在一個事務內兩次讀到的資料時是不一樣的。)
3.幻讀
一個事務根據相同的查詢條件,重新執行查詢,返回的記錄中包含與前一次執行查詢返回的記錄不同的行。
以上這三種 情況都是同時進行的幾個事務對相同的資料進行讀取時造成的。
如何處理這幾種異常呢?
ANSI SQL-92標準中定義了以下幾種事務隔離級別:
1.Read Uncommitted
最低等級的事務隔離,僅僅保證讀取過程中不會讀到非法資料。(三種異常情況均可能發生)
2.Read Committed
避免了“髒讀”。一個select查詢只能檢視到查詢開始之前提交的資料。在查詢執行時,其他事務修改或者提交的資料它看不到(資料庫預設的事務隔離級別)
3.Repeatable Read
避免了“髒讀”和“不可重複讀”。一個事務不可能更新由另一個事務讀取但是沒有提交的資料。應用並不廣泛。因為它可能出現幻讀。同時帶來更多效能損失。
4.Serializable
ORACLE資料庫只支援Read Committed 和Serializable兩種隔離級別。另外自定義了一個Read Only的隔離級別,只允許讀,不允許改。避免不可重複讀和幻讀
三種都能避免。所有事務序列,而不是並行。
Spring事務的隔離級別 1. ISOLATION_DEFAULT: 這是一個PlatfromTransactionManager預設的隔離級別,使用資料庫預設的事務隔離級別. 另外四個與JDBC的隔離級別相對應 2. ISOLATION_READ_UNCOMMITTED: 這是事務最低的隔離級別,它充許令外一個事務可以看到這個事務未提交的資料。 這種隔離級別會產生髒讀,不可重複讀和幻像讀。 3. ISOLATION_READ_COMMITTED: 保證一個事務修改的資料提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的資料 4. ISOLATION_REPEATABLE_READ: 這種事務隔離級別可以防止髒讀,不可重複讀。但是可能出現幻像讀。 它除了保證一個事務不能讀取另一個事務未提交的資料外,還保證了避免下面的情況產生(不可重複讀)。 5. ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。 除了防止髒讀,不可重複讀外,還避免了幻像讀。
使用Spring AOP實現宣告式事務管理
1.基於XML配置(使用較多)
(1)配置事務管理類
-
<!-- 定義事務管理器 -->
-
<bean id="transactionManager"
-
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
-
<property name="dataSource" ref="dataSource" />
-
</bean>
在spring的配置中配置資料來源(dataSource)、事務管理器,事務管理器使用不同的orm框架事務管理器類就不同,mybatis 是org.springframework.jdbc.datasource.DataSourceTransactionManager 。而hibernate事務管理器為org.springframework.orm.hibernate3.HibernateTransactionManager
(2)配置事務屬性
-
<!-- 配置事務的屬性 -->
-
<tx:advice id="TestAdvice" transaction-manager="transactionManager">
-
<!--配置事務傳播性,隔離級別以及超時回滾等問題 -->
-
<tx:attributes>
-
<tx:method name="search*" propagation="REQUIRED" read-only="true" isolation="DEFAUT" TIMEOUT="-1" />
-
<tx:method name="del*" propagation="REQUIRED" />
-
<tx:method name="update*" propagation="REQUIRED" />
-
<tx:method name="add*" propagation="REQUIRED" />
-
</tx:attributes>
-
</tx:advice>
事務屬性在<tx:method>中進行設定,Spring支援對不同的方法設定不同的事務屬性,所以可以為一個<tx:advice>設定多個<tx:method>,其中name屬性指定匹配的方法(這裡需要對這些方法名進行約定,如果事務切入點在service上,則最好和Dao的方法命名區分開,也不要使用get set關鍵字,防止和屬性的getter setter發生混淆)
事務有以下幾個常用屬性:
a.read-only:設定該事務中是否允許修改資料。(對於只執行查詢功能的事務,設定為TRUE可以提高事務的執行速度)
b.propagation:事務的傳播機制。一般設定為required。可以保證在事務中的程式碼只在當前事務中執行,防止建立多個事務。
c.isolation:事務隔離級別。不是必須的。預設值是default。
d.timeout:允許事務執行的最長時間,以秒為單位。
e.rollback-for:觸發回滾的異常。
f.no-rollback-for:不會觸發回滾的異常。
***實際開發中,對於只執行查詢功能的事務,要設定read-only為TRUE,其他屬性一般使用預設值即可。
(3)配置事務的AOP切入點
-
<aop:config>
-
<!--配置事務切點 -->
-
<aop:pointcut id="services"
-
expression="execution(public* com.pb.service.*.*(..))" />
-
<aop:advisor pointcut-ref="services" advice-ref="TestAdvice" />
-
</aop:config>
該設定的含義是:對於com.pb.service.impl包及子包下的所有類的所有公共方法進行切入。(被切入的 方法經過<tx:method>篩選)web應用程式最合適的事務切入點是Service的方法上。
----通過以上三個步驟設定好宣告式事務後,當Service中 的業務方法被呼叫之前,Spring會獲取事務物件並啟動事務。並使用try-catch-finally來處理異常。業務方法執行成功則會提交事務,預設情況下如果丟擲了RuntimeException 或者Rrror 物件就會回滾事務。(注意: 這裡注意一下,在tx:method中配置了rollback_for 中配置的Exception 這個是執行時的異常才會回滾不然其他異常是不會回滾的!)
2.使用annotation配置
*1.在事務管理的dao實現類之前標註@Transactional
*2.在要進行事務管理的方法前加上@Transactional(propagation= Propagation.REQUIRED)
*3.在配置檔案中指定驅動:<tx:annotation-driven transaction-manager="transactionManager" />
-
package demo.spring.dao;
-
import java.util.Iterator;
-
import java.util.List;
-
import javax.sql.DataSource;
-
import org.springframework.jdbc.core.JdbcTemplate;
-
import org.springframework.transaction.annotation.Propagation;
-
import org.springframework.transaction.annotation.Transactional;
-
import demo.spring.entity.Person;
-
@Transactional//將此類進行事務管理
-
public class PersonDaoImpl implements PersonDao {
-
private JdbcTemplate jt;
-
public void setDataSource(DataSource dataSource){
-
jt = new JdbcTemplate(dataSource);
-
}
-
@Override
-
public void insert(long id, String name, int age) {
-
jt.update("insert into person values('"+id+"','"+name+"','"+age+"')");
-
}
-
@Transactional(propagation= Propagation.REQUIRED)//定義要事務管理的方法,指定傳播行為
-
public void batchInsert(List persons) {
-
for(Iterator it = persons.iterator(); it.hasNext(); ){
-
Person p = (Person) it.next();
-
insert(p.getId(),p.getName(),p.getAge());
-
}
-
}
-
}