1. 程式人生 > >Spring入門第二十九課

Spring入門第二十九課

mchange method true ide ash 一個 value import 重復

事務的隔離級別,回滾,只讀,過期

當同一個應用程序或者不同應用程序中的多個事務在同一個數據集上並發執行時,可能會出現許多意外的問題。

並發事務所導致的問題可以分為下面三種類型:

-臟讀

-不可重復讀

-幻讀

看代碼:

db.properties

jdbc.user=root
jdbc.password=logan123
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/spring

jdbc.initPoolSize=5
jdbc.maxPoolSize=10

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"
    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-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"
> <context:component-scan base-package="logan.study.spring.tx"></context:component-scan> <!-- 導入資源文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置C3P0數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!-- 配置Spring的JDBCTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置NamedParameterJdbcTemplate,該對象可以使用具名參數,其沒有無參的構造器,所以必須為其構造器指定參數 --> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 啟用事務註解 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
package logan.study.spring.tx;

public interface BookShopDao {
    //根據書號獲取書的單價
    public int findBookPriceIsbn(String isbn);
    
    //更新書的庫存,使書號對應的庫存-1
    public void updateBookStock(String isbn);
    
    
    public void updateUserAccount(String username,int price);

}
package logan.study.spring.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int findBookPriceIsbn(String isbn) {
        // TODO Auto-generated method stub
        String sql = "SELECT price FROM book WHERE isbn=?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        // TODO Auto-generated method stub
        //檢查書的庫存是否足夠,若不夠,則拋出異常
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if(stock == 0){
            throw new BookStockException("庫存不足!");
        }
        String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
        jdbcTemplate.update(sql, isbn);

    }

    @Override
    public void updateUserAccount(String username, int price) {
        // TODO Auto-generated method stub
        //檢查書的庫存是否足夠,若不夠,則拋出異常
        String sql2 = "SELECT balance FROM account WHERE username = ?";
        int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
        if(balance < price){
            throw new UserAccountException("余額不足!");
        }
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        jdbcTemplate.update(sql, price, username);

    }

}
package logan.study.spring.tx;

public interface BookShopService {
    
    public void purchase(String username, String isbn);

}
package logan.study.spring.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
    
    @Autowired
    private BookShopDao bookShopDao;

    //添加事務註解
    @Transactional
    @Override
    public void purchase(String username, String isbn) {
        // TODO Auto-generated method stub
        //1.獲取書的單價
        int price = bookShopDao.findBookPriceIsbn(isbn);
        //2.更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //3.更新用戶余額
        bookShopDao.updateUserAccount(username, price);
        

    }

}
package logan.study.spring.tx;

import java.util.List;

public interface Cashier {
    public void checkout(String username,List<String> isbns);

}
package logan.study.spring.tx;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("cashier")
public class CashierImpl implements Cashier{
    
    @Autowired
    private BookShopService bookShopService;

    
    /**
     * 使用propagation指定事務的傳播行為,即當前的事務方法被另外一個事務方法調用時
     * 如何使用事務,默認取值為REQUIRED,即使用調用方法的事務
     * REQUIRES_NEW事務自己的事務,調用事務方法的事務被掛起
     * 使用isolation指定事務的隔離級別,最常用取值為READ_COMMITTED
     * 默認情況下,Spring的聲明式事務對所有的運行時異常進行回滾,也可以通過對應的屬性進行設置
     * 使用readonly 指定事務為只讀,表示這個事務只讀取數據單不更新數據,
     * 這樣可以幫助數據庫引擎優化事務。若真的是一個只讀數據庫值的方法,應該設置readOnly=true
     * 使用timeout指定強制回滾之前事務可以占用的時間。
     */ 
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class})
    @Override
    public void checkout(String username, List<String> isbns) {
        // TODO Auto-generated method stub
        for(String isbn:isbns){
            bookShopService.purchase(username, isbn);
        }
        
    }

}

當同一個應用程序或者不同應用程序中的多個事務在同一 個數據集上並發執行時,可能會出現許多意外的問題 並發事務所導致的問題可以分為下面三種類型:

-臟讀:對於兩個事物T1,T2,T1讀取了已經被T2更新但還沒有被 提交的字段.之後,若T2回滾,T1讀取的內容就是臨時且無效的. -不可重復讀:對於兩個事物T1,T2T1讀取了一個字段,然後T2更 新了該字段.之後,T1再次讀取同一個字段,值就不同了.

-幻讀:對於兩個事物T1,T2,T1從一個表中讀取了一個字段,然後 T2在該表中插入了一些新的行.之後,如果T1再次讀取同一個表, 就會多出幾行.


Spring入門第二十九課