《Java Spring框架》Spring事務管理


1、  事務相關知識:

 什么是事務:把多條數據庫操作捆綁到一起執行,要么都成功,要么都失敗;

 事務的原則ACID:

       原子性:事務包含的所有操作,要么全部成功,要么全部失敗回滾,成功全部應用到數據庫,失敗不能對數據庫有任何影響;

       一致性:事務在執行前和執行后必須一致;例如A和B一共有100塊錢,無論A、B之間如何轉賬,他們的錢始終相加都是100;

       隔離性:多用戶並發訪問同一張表時,數據庫為每一個用戶開啟新的事務,該事務不能被其他事務所影響,相互有隔離;

       持久性:一個事務一旦提交,則對數據庫中數據的改變是永久的,即便系統故障也不會丟失;

並發可能引起的問題:

        臟讀:一個事務讀取到另一個事務未提交的數據;

        不可重復讀:一個事務讀取到另一個事務已提交(Update操作)的數據,導致前后讀取不一致;

        幻讀(虛讀):一個事務中讀取到別的事務插入(Insert操作)的數據,導致前后讀取不一致;

事務的隔離級別:根據實際情況選擇;

         Serializable串行化:可避免臟讀、不可重復讀和幻讀;

         Repeatable read可重復讀:可避免臟讀、不可重復讀;(MySql默認值)

         Read committed讀已提交:可避免臟讀;

         Read uncommitted讀未提交:任何情況都無法保證;

2、  Spring-aop事務-搭建環境;

事務基本操作:打開事務、提交事務、回滾事務;

Spring中利用接口來管理不同框架的事務操作;

     通過實現PlatformTransactionManager接口支持不同的框架完成各自的事務處理;

     為不同平台提供對應的事務管理器的實現:

     JDBC&Mybatis:DataSourceTransactionManager;

Spring-aop事務通過配置事務的隔離級別、事務傳播行為、是否只讀來操作;

      隔離級別:串行化、可重復讀、讀已提交、讀未提交;

      是否只讀:true:不可改變數據庫中的數據,查詢操作推薦;  false:可以改變數據庫數據;

      事務傳播行為:事務方法嵌套調用的規則: xService.x();  ->     yService.y();

            REQUIRED:如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,該設置是最常用的設置;

            REQUIRES_NEW:創建新事務,無論當前存不存在事務,都創建新事務;

            SUPPORTS:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行;

            NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起(暫停);

            MANDATORY:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常;

            NEVER:以非事務方式執行,如果當前存在事務,則拋出異常;

            NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與REQUIRED類似的操作。

3、  Spring-aop事務 – 從麻煩的事務代碼中走出之xml配置版aop事務;

使用經典的轉賬案例進行測試,准備數據:bean、service、dao;

使用事務需要額外導入tx包和tx約束;

配置事務核心管理器: DataSourceTransactionManager;

配置事務通知 tx:Advice;

配置aop;

根據以上知識點,我們來實現spring的事務管理。

數據庫創建一張表:

 代碼:

/**
 * 賬戶
 * @author hubt11585
 */
public class Account {
    private Integer id;
    private String name;
    private Double money;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getMoney() {
        return money;
    }
    public void setMoney(Double money) {
        this.money = money;
    }
}
public interface AccountDao {
    //扣款
    void subMoney(Integer id, Double money);
    //加款
    void addMoney(Integer id, Double money);
}
import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    @Override
    public void subMoney(Integer id, Double money) {
        String sql = "update account set money = money - ? where id = ?";
        getJdbcTemplate().update(sql, money, id);
    }

    @Override
    public void addMoney(Integer id, Double money) {
        String sql = "update account set money = money + ? where id = ?";
        getJdbcTemplate().update(sql, money, id);
    }
}
/**
 * 賬戶service
 * @author Joey
 *
 */
public interface AccountService {
    
    //轉賬接口
    void transferAccounts();
}
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.tao.dao.AccountDao;

@Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=true)
public class AccountServiceImpl implements AccountService {

    //賬戶dao
    private AccountDao ad;
    public void setAd(AccountDao ad) {
        this.ad = ad;
    }

    @Override
    @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=false)
    public void transferAccounts() {
        //轉賬邏輯
        
        //先從A賬戶扣款
        ad.subMoney(1, 50d);
        int a = 1/0;      // 除以0會出現異常
        //再給B賬戶加款
        ad.addMoney(2, 50d);
    }
}
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;

import com.tao.service.AccountService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TxTest {
    
    @Resource(name="accountService")
    private AccountService as;
    @Test
    public void Test1() {
        as.transferAccounts();
    }
}

配置文件:applicationContext.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:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <!-- 依賴關系 dao ->  -> dataSource -->
    <!-- 讀取配置文件 -->
    <context:property-placeholder location="db.properties"/>
    
    <!-- 配置 dataSource -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="user" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

    <!-- dao -->
    <bean name="accountDao" class="com.tao.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- service -->
    <bean name="accountService" class="com.tao.service.AccountServiceImpl">
        <property name="ad"  ref="accountDao"/>
    </bean>
    
    <!-- 配置事務核心管理器 不同平台不一樣 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 事務通知 -->
    <tx:advice id="txAdivce" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transferAccounts" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="save*" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="delete*" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="update*" isolation="DEFAULT"  propagation="REQUIRED" read-only="false"/>
            <tx:method name="select*" isolation="DEFAULT"  propagation="REQUIRED" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置aop -->
    <aop:config>
        <aop:pointcut expression="execution(* com.tao.service.*ServiceImpl.*(..))" id="txPc"/>
        <aop:advisor advice-ref="txAdivce" pointcut-ref="txPc"/>
    </aop:config>
</beans>

運行結果:

會出現異常

表里面數據被回滾,並沒有出現執行了異常前面部分,效果達到。

以下是注解版代碼:

只需要調整一下部分代碼。

配置文件中:開啟事務:

<!-- 開啟注解事務 -->   <tx:annotation-driven/>

完整配置文件如下:

<?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:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    <!-- 依賴關系 dao ->  -> dataSource -->
    <!-- 讀取配置文件 -->
    <context:property-placeholder location="db.properties"/>
    
    <!-- 配置 dataSource -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- dao -->
    <bean name="accountDao" class="com.tao.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- service -->
    <bean name="accountService" class="com.tao.service.AccountServiceImpl">
        <property name="ad"  ref="accountDao"/>
    </bean>
    
    <!-- 配置事務核心管理器 不同平台不一樣 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 開啟注解事務 -->
    <tx:annotation-driven/>
    
</beans>

代碼:AccountServiceImpl類上(整個類下所有方法有效)或者方法上加上注解即可實現統一事務。

import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.tao.dao.AccountDao;

@Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=true)
public class AccountServiceImpl implements AccountService {

    //賬戶dao
    private AccountDao ad;
    public void setAd(AccountDao ad) {
        this.ad = ad;
    }

    @Override
    @Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRED, readOnly=false)
    public void transferAccounts() {
        //轉賬邏輯
        
        //先從A賬戶扣款
        ad.subMoney(1, 50d);
        int a = 1/0;      // 除以0會出現異常
        //再給B賬戶加款
        ad.addMoney(2, 50d);
    }
}

運行結果:

會出現異常

表里面數據被回滾,並沒有出現執行了異常前面部分,效果達到。

 總結:通過spring來控制事務,方便整潔。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM