1. 程式人生 > >Spring中的事務操作

Spring中的事務操作

val user 重復 具體實現 BE tca har IT point

事務的特性

  • 原子性:強調事務的不可分割。
  • 一致性:事務的執行的前後數據的完整性保持一致。
  • 隔離性:一個事務執行的過程中,不應該受到其他事務的幹擾。
  • 持久性:事務一旦結束,數據就持久化到數據庫。

如果不考慮隔離性會引發的安全性問題

  • 臟讀:一個事務讀到了另一個事務的未提交的數據。
  • 不可重復讀:一個事務讀到了另一個事務已經提交的update的數據,導致多次查詢的結果不一致。
  • 虛讀:一個事務讀到了另一個事務已經提交的insert的數據,導致多次查詢的結果不一致。

解決讀問題:設置事務的隔離級別

  • 未提交讀:臟讀、不可重復讀和虛讀都有可能發生。
  • 已提交讀:避免臟讀,但是不可重復讀和虛讀有可能發生。
  • 可重復讀:避免臟讀和不可重復讀,但是虛讀有可能發生。
  • 串行化的:避免以上所有讀問題。

Spring的聲明式事務管理方式

Spring進行聲明式事務配置的方式有兩種:

  1. 基於xml配置文件方式
  2. 基於註解方式

但無論使用什麽方式進行Spring的事務操作,首先要配置一個事務管理器。

搭建轉賬的環境

第一步,創建數據庫表。

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` int(11) DEFAULT NULL,
  `username` varchar(100) DEFAULT NULL,
  `salary` 
int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `account` VALUES (1, 小鄭, 10000); INSERT INTO `account` VALUES (2, 小譚, 10000);

第二步,創建一個Web項目,並引入Spring的相關jar包。

技術分享圖片

第三步,創建業務層和DAO層的類。
在Web項目的src目錄下創建一個cn.itcast.tx包,並在該包下編寫業務層和DAO層的類。

  • 業務層——BookService.java
public class BookService {

    
private BookDao bookDao; public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
  • DAO層——BookDao.java
public class BookDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

}

第四步,配置業務層和DAO層的類。

<?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">

    <!-- 配置C3P0連接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///spring_lee"></property>
        <property name="user" value="root"></property>
        <property name="password" value="yezi"></property>
    </bean>

    <!-- 創建service和dao的對象 -->
    <bean id="bookService" class="cn.itcast.tx.BookService">
        <!-- 註入dao -->
        <property name="bookDao" ref="bookDao"></property>
    </bean>
    <bean id="bookDao" class="cn.itcast.tx.BookDao">
        <!-- 註入JdbcTemplate模板類的對象 -->
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <!-- 創建JdbcTemplate模板類的對象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 註入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

第五步,轉賬的具體實現,實現小鄭轉賬1000元給小譚。
JavaEE中DAO層做的事情主要是對數據庫進行操作,在DAO層裏面一般不寫業務操作,一般寫單獨操作數據庫的方法。所以BookDao類的代碼要修改為:

public class BookDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // 小鄭少1000
    public void lessMoney() {
        String sql = "update account set salary=salary-? where username=?";
        jdbcTemplate.update(sql, 1000, "小鄭");
    }

    // 小譚多1000
    public void moreMoney() {
        String sql = "update account set salary=salary+? where username=?";
        jdbcTemplate.update(sql, 1000, "小譚");
    }
}

JavaEE中Service層寫具體的業務操作,所以BookService類的代碼要修改為:

public class BookService {

    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    // 轉賬的業務
    public void accountMoney() {
        // 1.小鄭少1000
        bookDao.lessMoney();

        // 2.小譚多1000
        bookDao.moreMoney();
    }
}

第六步,編寫一個測試類。
在cn.itcast.tx包下編寫一個TestDemo單元測試類。

public class TestDemo {

    @Test
    public void testAccount() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        BookService bookService = (BookService) context.getBean("bookService");
        bookService.accountMoney();
    }
}

測試以上方法即可實現小鄭轉賬1000元給小譚。現在我來演示一個問題,在BookService類中調用BookDao類的兩個方法構成了轉賬業務,但是如果小鄭少了1000元之後,這時突然出現異常,比如銀行斷電,就會出現小鄭的錢少了,而小譚的錢沒有多,錢丟失了的情況。

public class BookService {

    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    // 轉賬的業務
    public void accountMoney() {
        // 1.小鄭少1000
        bookDao.lessMoney();

        int x = 10 / 0; // 模擬銀行斷電的情況(出現的異常)

        // 2.小譚多1000
        bookDao.moreMoney();
    }
}

這時應該怎麽解決這個問題呢?就可使用事務來解決。Spring中進行事務的操作主要有兩種方式:

    1. 第一種:編程式事務管理(這種了解就行,不用掌握)
    2. 第二種:聲明式事務管理
      • 基於xml配置文件方式
      • 基於註解方式

Spring的聲明式事務管理——XML方式:思想就是AOP

基於xml配置文件的方式來進行聲明式事務的操作,不需要進行手動編寫代碼,通過一段配置完成事務管理。

第一步,配置事務管理器。
Spring針對不同的持久化框架,提供了不同PlatformTransactionManager接口的實現類,如下:

技術分享圖片

所以我們需要在Spring的配置文件中添加如下配置:

<!-- 1.配置事務的管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 指定要對哪個數據庫進行事務操作 -->
    <property name="dataSource" ref="dataSource"></property>
</bean>

第二步,配置事務的增強,即指定對哪個事務管理器進行增強。故需要向Spring的配置文件中添加如下配置:

<!-- 2.配置事務的增強,指定對哪個事務管理器進行增強 -->
<tx:advice id="txadvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--
            表示來配置你要增強的方法的匹配的一個規則,
            註意:只須改方法的命名規則,其他都是固定的!
            propagation:事務的傳播行為。
        -->
        <tx:method name="account*" propagation="REQUIRED"></tx:method>
        <!-- <tx:method name="insert*" propagation="REQUIRED"></tx:method> -->
    </tx:attributes>
</tx:advice>

第三步,配置切入點和切面。這步須向Spring的配置文件中添加如下配置:

<!-- 3.配置切入點和切面(最重要的一步) -->
<aop:config>
    <!-- 切入點 -->
    <aop:pointcut expression="execution(* cn.itcast.tx.BookService.*(..))" id="pointcut1"/>
    <!-- 切面,即表示把哪個增強用在哪個切入點上 -->
    <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1"/>
</aop:config>

這時測試TestDemo單元測試類的testAccount方法即可。

Spring的聲明式事務的註解方式

基於註解方式來進行聲明式事務的操作會更加簡單,在實際開發中我們也會用的比較多。
第一步,配置事務管理器。

<!-- 1.配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

第二步,開啟事務註解。

<!-- 2.開啟事務的註解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

以上配置添加完畢之後,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">

    <!-- 配置C3P0連接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///spring_lee"></property>
        <property name="user" value="root"></property>
        <property name="password" value="yezi"></property>
    </bean>

    <!-- 1.配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 2.開啟事務的註解 -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

    <!-- 創建service和dao的對象 -->
    <bean id="bookService" class="cn.itcast.tx.BookService">
        <!-- 註入dao -->
        <property name="bookDao" ref="bookDao"></property>
    </bean>
    <bean id="bookDao" class="cn.itcast.tx.BookDao">
        <!-- 註入JdbcTemplate模板類的對象 -->
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <!-- 創建JdbcTemplate模板類的對象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 註入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

第三步,在具體使用事務的方法所在的類上面添加註解:@Transactional。即BookService類應修改為:

@Transactional
public class BookService {

    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    // 轉賬的業務
    public void accountMoney() {
        // 1.小鄭少1000
        bookDao.lessMoney();

        int x = 10 / 0; // 模擬銀行斷電的情況(出現的異常)

        // 2.小譚多1000
        bookDao.moreMoney();
    }
}

這時測試TestDemo單元測試類的testAccount方法即可。

Spring中的事務操作