1. 程式人生 > >Spring 學習 (七) 宣告式事務管理

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);

   }

這裡寫圖片描述