Spring 學習 (七) 宣告式事務管理
先來回顧一下事務
事務這個概念一開始是在資料庫中被提起的
事務的特性:
ACID
原子性:指事務是一個不可分割的工作單位,事務的操作要麼都發生,要麼都不發生
一致性:指事務前後資料的完整性必須保證一致
隔離性:指多個使用者併發訪問資料庫時,一個使用者的事務不能被其他使用者的事務所幹擾,多個併發事務之間資料要相互隔離
永續性:指一個事務一旦被提交,他對資料庫中資料的改變就是永久性的,即時資料庫發生故障也不應該對其有任何影響
隔離性的解釋有點繞,所以百度下找個容易理解的說法:
比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離。
即要達到這麼一種效果:對於任意兩個併發的事務A和B,在事務A看來,B要麼在A開始之前就已經結束,要麼在A結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。
所以說事務的隔離很重要,如果沒有隔離,會發生什麼呢
髒讀:一個事務讀取了另一個事務改寫但是還沒有提交的資料,如果這些資料被回滾,則讀到的資料是無效的
不可重複讀:在同一個事務中,多次讀取同一個資料但是返回的結果有所不同
幻讀:一個事務讀取了幾行記錄後,另一個事務插入了一些記錄,幻讀由此產生。然後再次查詢,第一個事務就會發現有些原來沒有的記錄
幻讀和不可重複讀區別:
不可重複讀:例如在讀取資料時,別的事務對其所讀資料提交的修改,所以在此讀取發現不一樣
幻讀:例如讀取時,別的事務又提交了新增資料
如何解決這些問題(隔離機制)
READ_UNCOMMITED(讀未提交) 允許讀取還未提交的改變了的資料。 會導致:髒讀,幻讀,不可重複讀
READ_COMMITTED:(讀已提交) 允許在併發事務已經提交後讀取。可防止髒讀,但幻讀,不可重複讀仍可發生
REPEATABLE_READ:(可重複讀) 對相同欄位多次讀取是一樣的,除非資料被事務本身改變,可防止髒,不可重複讀,但幻讀認可發生
SERIALIZABLE:(序列化) 完全服從acid隔離級別,確保不發生 髒 幻 不可重複讀,但是效率是最慢的
複習了事務後,開始Spring 事務的學習
先了解Spring為什麼要有事務?
因為在不同平臺,操作事務的程式碼各不相同.spring提供了一個介面PlaformTransactionManager
Spring事務操作物件
PlaformTransactionManager聲明瞭事務中有哪些操作,並且針對不同平臺給出不同的實現類
例:DataSourceTransactionManager HibernateTransactionManager
注意:在Spring中使用事務管理,最為核心的物件是TransactionManager物件
Spring管理事務的屬性
事務隔離級別:
是否只讀
事務傳播行為(決定業務方法相互呼叫,事務該如何處理)
PROPAGION_XXX :事務的傳播行為
* 保證同一個事務中
PROPAGATION_REQUIRED 支援當前事務,如果不存在 就新建一個(預設)
PROPAGATION_SUPPORTS 支援當前事務,如果不存在,就不使用事務
PROPAGATION_MANDATORY 支援當前事務,如果不存在,丟擲異常
* 保證沒有在同一個事務中
PROPAGATION_REQUIRES_NEW 如果有事務存在,掛起當前事務,建立一個新的事務
PROPAGATION_NOT_SUPPORTED 以非事務方式執行,如果有事務存在,掛起當前事務
PROPAGATION_NEVER 以非事務方式執行,如果有事務存在,丟擲異廠
PROPAGATION_NESTED 如果當前事務存在,則巢狀事務執行
下面放一個案例(後面加事務要用):
模擬轉賬程式
dao介面
public interface AccountDao {
//加錢
void addMoney ( Integer id , Double money ) ;
//減錢
void decreaseMoney ( Integer id , Double money ) ;
}
dao實現
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao
{
//加錢
public void addMoney(Integer id, Double money)
{
String sql = "update MoneyDemo set money = money + ? where id = ?" ;
super.getJdbcTemplate().update( sql , money , id ) ;
}
//減錢
public void decreaseMoney(Integer id, Double money)
{
String sql = "update MoneyDemo set money = money - ? where id = ?" ;
super.getJdbcTemplate().update( sql , money , id ) ;
}
}
service介面
public interface AccountService {
//轉賬
void transfer ( Integer from , Integer to , Double money ) ;
}
service介面實現
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao ;
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(Integer from, Integer to, Double money) {
//減錢
accountDao.decreaseMoney(from, money);
//int i = 1 / 0 ;
//加錢
accountDao.addMoney(to, money);
}
}
bean.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:p="http://www.springframework.org/schema/p"
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:component-scan base-package="cn.itcast"></context:component-scan>
<!-- 指定Spring讀取DB.properties配置 -->
<context:property-placeholder location="classpath:DB.properties"/>
<!-- 將資料庫連線池交由Spring管理 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- dao -->
<bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
什麼叫宣告式事務管理?
Spring提供了對事務的管理, 這個就叫宣告式事務管理。
Spring宣告式事務管理,核心實現就是基於Aop。
Spring 管理事務的方式(3種):
編碼式
xml配置(aop)
註解(aop)
注意Spring管理事務是用aop來實現的 所以當你的類實現介面的話,接收時也要是用介面型別
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:p="http://www.springframework.org/schema/p"
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:component-scan base-package="cn.itcast"></context:component-scan>
<context:component-scan base-package="com.study.spring"></context:component-scan>
<!-- 指定Spring讀取DB.properties配置 -->
<context:property-placeholder location="classpath:DB.properties"/>
<!-- 將資料庫連線池交由Spring管理 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- dao -->
<bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 事務核心管理器,封裝了所有事務操作,依賴於連線池 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事務通知 -->
<tx:advice transaction-manager="txManager" id="tx">
<tx:attributes>
<!-- 配置針對特定方法應用特定事務屬性(*是萬用字元) isolation 隔離級別 propagation 傳播行為 -->
<tx:method name="transfer*" isolation="DEFAULT" propagation="REQUIRED" />
<tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" />
<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!--
將通知織入 -->
<aop:config>
<!-- 使用切入點表示式來定位切入點 -->
<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" id="pt"/>
<!-- 配置切面 ( 切面是由 通知+切入點 構成的 ) -->
<aop:advisor advice-ref="tx" pointcut-ref="pt"/>
</aop:config>
</beans>
測試案例
@RunWith(SpringJUnit4ClassRunner.class)//使用幫我們自動建立容器,就不用自己手動建立Spring容器
@ContextConfiguration("classpath:bean.xml")//指定配置檔案路徑
public class MoneyDemo {
@Resource(name="accountService")
private AccountService asi ;
@Test
public void fun1 ()
{
asi.transfer(1, 2, (double) 200);
}
}
註解方式
<?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:p="http://www.springframework.org/schema/p"
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:component-scan base-package="cn.itcast"></context:component-scan>
<context:component-scan base-package="com.study.spring"></context:component-scan>
<!-- 指定Spring讀取DB.properties配置 -->
<context:property-placeholder location="classpath:DB.properties"/>
<!-- 事務核心管理器,封裝了所有事務操作,依賴於連線池 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 將資料庫連線池交由Spring管理 -->
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- dao -->
<bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 註解方式實現事務: 指定註解方式實現事務 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
service
註解定義到方法上: 當前方法應用spring的宣告式事務
定義到類上: 當前類的所有的方法都應用Spring宣告式事務管理;
定義到父類上: 當執行父類的方法時候應用事務。
@Transactional
public void transfer(Integer from, Integer to, Double money) {
//減錢
accountDao.decreaseMoney(from, money);
// int i = 1 / 0 ;
//加錢
accountDao.addMoney(to, money);
}