事務(wù)
事務(wù)的基本概念
事務(wù)指的是邏輯上的一組操作医清,這組操作要么全部成功放可,要么全部失敗法褥。
事務(wù)的特性
原子性:事務(wù)是一個(gè)不可分割的工作單位茫叭,事務(wù)中的操作要么都發(fā)生,要么都不發(fā)生半等。
一致性:事務(wù)前后數(shù)據(jù)的完整性必須保持一致揍愁。
隔離性:多個(gè)用戶并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí)呐萨,一個(gè)用戶的事務(wù)不能被其他用戶的事務(wù)所干擾,多個(gè)并發(fā)事務(wù)之間數(shù)據(jù)要相互隔離(數(shù)據(jù)庫(kù)中相應(yīng)的數(shù)據(jù)隔離級(jí)別莽囤,通過(guò)它避免事務(wù)間的沖突)谬擦。
持久性:一個(gè)事務(wù)一旦被提交,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變是永久性的朽缎,即使數(shù)據(jù)庫(kù)發(fā)生故障也不應(yīng)該對(duì)其有任何影響惨远。
Spring中的事務(wù)管理
Spring提供了一組接口進(jìn)行事務(wù)的管理。
Spring提供事務(wù)管理的3個(gè)接口:
【1】PlatformTransactionManager:事務(wù)管理器话肖,用來(lái)管理事務(wù)的接口北秽,定義了事務(wù)的提交、回滾等方法最筒。
【2】TransactionDefinition:事務(wù)定義信息(隔離級(jí)別羡儿、傳播行為、是否超時(shí)是钥、是否只讀)
【3】TransactionStatus:事務(wù)具體運(yùn)行狀態(tài)(事務(wù)是否提交掠归,事務(wù)是否有保存點(diǎn),事務(wù)是否是新事物等狀態(tài))悄泥。
Spring事務(wù)管理時(shí)虏冻,這三個(gè)接口是有聯(lián)系的,Spring首先會(huì)根據(jù)事務(wù)定義信息TransactionDefinition獲取信息,然后由事務(wù)管理器PlatformTransactionManager進(jìn)行管理弹囚,在事務(wù)管理過(guò)程中厨相,會(huì)產(chǎn)生一個(gè)事務(wù)的狀態(tài),這個(gè)狀態(tài)就保存在事務(wù)具體運(yùn)行狀態(tài)TransactionStatus中了鸥鹉。
PlatformTransactionManager接口介紹:
通過(guò)Spring的API可以知道該接口有許多實(shí)現(xiàn)類例如:DataSourceTransactionManager蛮穿、HibernateTransactionManager等。Spring會(huì)為不同的持久化框架提供了不同PlatformTransactionManager接口實(shí)現(xiàn)毁渗。
比如當(dāng)我們使用SpringJDBC或者iBatis進(jìn)行持久化數(shù)據(jù)時(shí)使用DataSourceTransactionManager践磅。
TransactionDefinition定義事務(wù)隔離級(jí)別
該接口還提供了一些方法,例如:獲得隔離級(jí)別灸异、獲得超時(shí)信息府适、獲得是否只是只讀的等。
如果不考慮隔離性肺樟,就會(huì)引發(fā)安全問(wèn)題:臟讀檐春、不可重復(fù)讀、以及虛讀或者叫做幻讀么伯。
臟讀:一個(gè)事務(wù)讀取了另一個(gè)事務(wù)改寫但還未提交的數(shù)據(jù)疟暖,如果這些數(shù)據(jù)被回滾,則讀到的數(shù)據(jù)是無(wú)效的。
不可重復(fù)讀:同一事務(wù)中俐巴,多次讀取同一數(shù)據(jù)返回的結(jié)果有所不同(讀取到另一個(gè)事務(wù)已經(jīng)提交的更新的數(shù)據(jù))朋贬。
幻讀:一個(gè)事務(wù)讀取了幾行記錄后,另一個(gè)事務(wù)插入一些記錄窜骄,幻讀就發(fā)生了锦募。再后來(lái)的查詢中,第一個(gè)事務(wù)就會(huì)發(fā)現(xiàn)有些原來(lái)沒(méi)有的記錄邻遏。
正常情況下糠亩,數(shù)據(jù)庫(kù)提供了四種隔離級(jí)別:
READ_UNCOMMITED:安全級(jí)別最低,如果設(shè)置為該級(jí)別准验,就可能會(huì)發(fā)生臟讀赎线、不可重復(fù)讀、幻讀等糊饱。
READ_COMMITED:如果設(shè)置該級(jí)別垂寥,可以避免臟讀的發(fā)生,但是可能會(huì)發(fā)生不可重復(fù)讀和幻讀另锋。
REPEATABLE_READ:如果設(shè)置該級(jí)別滞项,可以避免臟讀和不可重復(fù)讀,但是可能會(huì)發(fā)生幻讀夭坪。
SERIALIZABLE:事務(wù)是串行的文判,不會(huì)發(fā)生并發(fā)訪問(wèn)這種情況
Spring提供了DEFAULT,它代表使用數(shù)據(jù)庫(kù)默認(rèn)的隔離級(jí)別(例如:Mysql默認(rèn)采用REPEATABLE_READ隔離級(jí)別,Oracle默認(rèn)采用READ_COMMITTED隔離級(jí)別)室梅。
TransactionDefinition定義事務(wù)傳播行為
事務(wù)的傳播行為:解決業(yè)務(wù)層方法之間相互調(diào)用的問(wèn)題(一個(gè)service層里的方法調(diào)用另一個(gè)service里中的方法戏仓,這兩個(gè)service中又分屬于兩個(gè)不同的事務(wù),傳播行為就是為了解決方法調(diào)用時(shí)事務(wù)的傳遞)亡鼠。
事務(wù)的傳播行為有7種赏殃,可以為3類:
第一類為前三個(gè),重點(diǎn)掌握第一個(gè)(在相同事務(wù)里):支持當(dāng)前事務(wù)(Service中bbb()調(diào)用Service中aaa()方法時(shí)间涵,如果aaa()有事務(wù)仁热,則使用該事務(wù)。如果沒(méi)有事務(wù)浑厚,則使用bbb()當(dāng)前事務(wù)股耽,如果當(dāng)前bbb()也沒(méi)有事務(wù),就會(huì)新創(chuàng)建一個(gè)事務(wù))
第二類為接下來(lái)三個(gè)钳幅,重點(diǎn)掌握第一個(gè)(在不同事務(wù)中):如果aaa()有事務(wù)存在,掛起當(dāng)前事務(wù)炎滞,創(chuàng)建一個(gè)新的事務(wù)(aaa()和bbb()不在一個(gè)事務(wù)中)敢艰。
第三類:如果當(dāng)前事務(wù)存在,則嵌套事務(wù)執(zhí)行(執(zhí)行aaa()完后册赛,會(huì)使用事務(wù)的保存點(diǎn)钠导,在執(zhí)行bbb()時(shí)如果發(fā)生異常震嫉,可以回滾到設(shè)置的保存點(diǎn),也可以回滾到最初始的狀態(tài))牡属。
TransactionStatus接口介紹
TransactionStatus接口:提供了獲取事務(wù)狀態(tài)的方法(例如:hasSavepoint()事務(wù)是否有保存點(diǎn)票堵,isCompleted()事務(wù)是否已經(jīng)完成,isNewTransaction()是否是新的事務(wù))逮栅。
Spring事務(wù)管理的兩種方式
【1】編程式的事務(wù)管理:手動(dòng)在程序中編寫代碼實(shí)現(xiàn)事務(wù)管理悴势,實(shí)際應(yīng)用中很少使用,通過(guò)TransactionTemplate管理事務(wù)措伐。
【2】聲明式的事務(wù)管理:使用XML配置實(shí)現(xiàn)事務(wù)管理特纤,推薦使用(代碼侵入性最小)侥加,Spring的聲明式事務(wù)管理是通過(guò)AOP實(shí)現(xiàn)的(沒(méi)有代碼之前開(kāi)啟事務(wù)捧存,代碼完成后提交事務(wù))。
使用聲明式事務(wù)管理實(shí)現(xiàn)轉(zhuǎn)賬示例
搭建事務(wù)管理環(huán)境(模擬轉(zhuǎn)賬環(huán)境)
【a】創(chuàng)建表及插入記錄
【b】創(chuàng)建項(xiàng)目并引入jar包
【c】引入log4j.properties担败、applicationContext.xml昔穴、jdbc.properties配置文件。
【e】創(chuàng)建包結(jié)構(gòu)提前,編寫Dao及Service
package spring.demo3;
/*
* 轉(zhuǎn)賬案例Dao層接口
*/
public interface AccountDao {
public void outMoney(String out,Double money);
public void inMoney(String in,Double money);
}
package spring.demo3;
/*
* 轉(zhuǎn)賬案例的業(yè)務(wù)層接口
*/
public interface AccountService {
public void transfer(String out,String in,Double money);
}
【f】spring配置文件編寫
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部的屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置業(yè)務(wù)層類 -->
<bean id="accountService" class="spring.demo3.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置DAO的類 -->
<bean id="accountDao" class="spring.demo3.AccountDapImpl">
<!-- 注入連接池 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
【g】Dao中獲取JDBC模板
首先Dao實(shí)現(xiàn)類需要繼承JdbcDaoSupport類傻咖,該類中就會(huì)注入JDBC模板,該類中定義了JDBC模板岖研,并提供set方法只要Dao中注入模板就可以卿操。也可以通過(guò)注入連接池獲取JDBC模板。
【h】Dao方法具體編寫
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDapImpl extends JdbcDaoSupport implements AccountDao {
public void outMoney(String out, Double money) {
// TODO Auto-generated method stub
String sql="update acount set acount =acount - ? where name = ?";
this.getJdbcTemplate().update(sql,money,out);
}
public void inMoney(String in, Double money) {
// TODO Auto-generated method stub
String sql="update acount set acount =acount + ? where name = ?";
this.getJdbcTemplate().update(sql,money,in);
}
}
【i】Service中注入Dao孙援,并調(diào)用Dao中的方法
package spring.demo3;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/*
* 業(yè)務(wù)層實(shí)現(xiàn)類
*/
public class AccountServiceImpl implements AccountService
{
//注入轉(zhuǎn)賬的Dao的類
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer( String out, String in, Double money) {
accountDao.outMoney(out, money);
int i=1/0;
accountDao.inMoney(in, money);
}
}
【k】測(cè)試:由于Junit4和spring整合的包已經(jīng)引入害淤,所以通過(guò)注解@ContextConfiguration注解加載配置文件,這里使用注解@Resource(name="")方式注入AccountService拓售。
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/*
* Spring的聲明事務(wù)管理的方式二:基于AspectJ的xml方式配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class TestDemo3 {
@Resource(name="accountService")
private AccountService accountService;
@Test
/*
* 轉(zhuǎn)賬案例測(cè)試
*/
public void demo3(){
accountService.transfer("aaa", "bbb", 100d);
}
}
聲明式事物管理方式:基于AspectJ的XML方式
【1】引入AspectJ的jar包和整合AspectJ的包窥摄。
AspectJ簡(jiǎn)介:Spring在開(kāi)發(fā)AOP時(shí),為了簡(jiǎn)化編程而建立的础淤。(AspectJ實(shí)際是開(kāi)源的崭放、第三方的AOP開(kāi)發(fā)框架,Spring為簡(jiǎn)化自身開(kāi)發(fā)鸽凶,就引入了AspectJ的包)
【2】配置事物管理器币砂,并注入數(shù)據(jù)源dataSource。
<!-- 配置事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
【3】配置事物的通知
(事物的增強(qiáng),通過(guò)<tx:advice id="" transaction-manager="事物管理器Id"></tx:advice>玻侥,這個(gè)標(biāo)簽中需要配置事物相關(guān)的一些屬性<tx:attributes></tx:attributes>决摧,該屬性的作用就是哪些方法執(zhí)行事務(wù),<tx:method="方法名"></tx:method>如果多個(gè)方法可以用英文單詞*,該標(biāo)簽中還定義了事物的傳播行為、隔離級(jí)別、超時(shí)信息掌桩、只讀等等屬性)
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--
PROPAGATION:事務(wù)的傳播行為
ISOLATION:事務(wù)的隔離級(jí)別
readOnly:只讀
rollback-for:發(fā)生哪些異潮咚回滾
no-rollback-for:發(fā)生哪些異常不回滾
timeout:過(guò)期信息
-->
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
【4】配置AOP的切面
(通過(guò)<aop:config></aop:config>,在<aop:config>下需要配置切入點(diǎn)<aop:point-cut expression="" id=""></aop:point-cut>,其中表達(dá)式最前面的表示方法返回值為任意的,+號(hào)表示及其子類波岛,.表示任意的方法茅坛,(..)表示任意的參數(shù),接下來(lái)需要配置切面其中<aop:aspect>代表多個(gè)切入點(diǎn)则拷,多個(gè)通知贡蓖,而<aop:advisor>是一個(gè)切入點(diǎn),一個(gè)通知的隔躲。這里使用它即可摩梧。因?yàn)楝F(xiàn)在只有一個(gè)增強(qiáng)即事物增強(qiáng),這樣AccountService即其子類的任意方法都會(huì)事物增強(qiáng)了)
<aop:config>
<!--配置切入點(diǎn) -->
<aop:pointcut expression="execution(* spring.demo3.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
使用事務(wù)管理方式,一旦發(fā)生異常,整個(gè)事務(wù)不會(huì)執(zhí)行,不會(huì)出現(xiàn)錢轉(zhuǎn)丟的現(xiàn)象