Spring_5 聲明式事物

1、事務的概念

將一組操作數據的SQL作爲一個整體提交,要麼都成功,要麼都失敗

使用的原因:
保證數據的完整性和一致性,對數據操作是要保證數據的安全
2、事務的特性【ACID】

⑴ 原子性
將多個SQL作爲一個整體,不可分割

⑵ 一致性
數據在操作前是正確的,操作後也是正確的
操作的結果應該和業務邏輯保持一致

⑶ 隔離性
多個事務併發操作同一個數據時,保證事務間的數據是隔離開來的,相關不會受到干擾,保證數據的安全

⑷ 持久性
事務操作數據庫的結果,應該永久地保存到持久化存儲器中
3、事務的隔離級別
分類

未提交讀

int TRANSACTION_READ_UNCOMMITTED = 1;
一個事務讀取到了另一個事務還沒有提交的數據

可能產生髒讀,不可重複讀,幻讀問題。但是它解決了數據丟失問題

例如:

T1正在讀取數據,並對數據進行操作
T2 修改了數據,但是還沒有提交數據
T1 再次讀取數據,可能發生髒讀問題。因爲T2可能會回滾事務

已提交讀

int TRANSACTION_READ_COMMITTED = 2;
一個事務讀取到了另外一個事務已經提交過的數據

可能產生不可重複讀,幻讀問題。但是它解決了數據丟失和髒讀問題

例如:

T1 正在讀取數據,並對數據進行操作
T2 修改了數據,並提交了數據
T1 再次讀取數據,發現兩次讀取到的數據不一致。這就產生了不可重複讀問題

重複讀

int TRANSACTION_REPEATABLE_READ = 4;
一個事務只能重複的讀取當前事務中的操作數據,而不能讀取到另外的事務中未提交和已提交的數據

可能產生幻讀。但是它解決了數據丟失,髒讀和不可重複讀問題

例如:

 T1 正在讀取數據,並對數據進行操作
 T2 修改了數據,並且提交了數據,但是沒有提交數據
 T1 再次讀取數據,兩次讀取到的數據是一致的

序列化/不可併發

int TRANSACTION_SERIALIZABLE = 8;
可以解決所有的問題,一般配合數據庫鎖的機制控制數據的安全【統計工作】

幻讀問題:一個事務正在進行某種統計操作,其他事務又進行數據的增刪改等操作,導致兩次統計到的結果不一致

例如:

 T1 正在統計表的記錄數,得到一個結果
 T2 向數據庫中添加了一些數據
 T3 再次統計表的記錄數,得到的結果就比T1統計到的多

Tips:

SELECT * FROM ??? WHERE ??? FOR UPDATE; // 行級鎖

數據庫的默認隔離級別

MySQL 4(重複讀)
Oracle 2(已提交讀)

4、事務管理方式
編程式事務

即使用原生的JDBC API進行事務的管理

步驟:
⑴ 獲取數據庫連接對象【Connection】
⑵ 取消事務的自動提交
⑶ 執行SQL操作
⑷ 正常完成操作時,手動提交事務
⑸ 執行失敗時,回滾事務
⑹ 關閉相關資源(釋放連接等)
聲明式事務

通過相關配置,給程序方法增加事務的操作
5、Spring的聲明式事務
相關API

Spring框架提供了PlatformTransactionManager接口,用來管理底層的事務。並且提供了相關的實現類

⑴ DataSourceTransactionManager
Spring和JdbcTemplate或MyBatis框架集成時,提供的事務管理器

⑵ HibernateTransactionManager
Spring和Hibernate框架集成時,提供的事務管理器
使用步驟

⑴ 拷貝jar包
① 和IOC有關的:

commons-logging-1.1.3.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar

② 和AOP有關的:

spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

③ 和數據庫有關的:

spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar

④ 數據庫驅動和c3p0

mysql-connector-java-5.1.7-bin.jar
c3p0-0.9.1.2.jar

⑵ 創建c3p0的properties配置文件
⑶ 創建核心配置文件
添加context和tx名稱空間

① 通過

<context:component-scan base-package="要掃描的包及其子包" />

標籤,來設置自動掃描包

② 通過

<context:property-placeholder location="???.properties" />

標籤,來引入外部配置文件

③ 通過bean 標籤,聲明c3p0即DataSource對象

④ 通過bean 標籤,聲明JdbcTemplate對象。注意需要給其dataSource屬性賦值,引用DataSource(c3p0)對象



⑤ 通過bean標籤,聲明TransactionManager對象。注意需要給其dataSource屬性賦值,引用DataSource(c3p0)對象



以DataSourceTransactionManager爲例

⑥ 通過

<tx:annotation-driven transaction-manager="事務管理器" />

標籤,來開啓基於註解的聲明式事務

⑷ 編寫具體的DAO、Service,並在具體的業務方法上,加上@Transactional註解,即可給方法開啓事務
6、Transactional【事務】的屬性
事務傳播行爲

【propagation屬性】
方法被調用時,事務的開啓方式

一共有7個傳播行爲,常用的有2個:
⑴ Propagation.REQUIRED
表示一個方法被調用時,如果已經存在了一個事務,則加入其中;否則,會開啓一個新的事務。【默認值】

⑵ Propagation.REQUIRES_NEW
表示一個方法被調用時,不管調用的方法是否存在事務,都會開啓一個新的事務
事務隔離級別

【isolation屬性】

Isolation.DEFAULT
表示與數據庫的默認隔離級別一致
MySQL 4(重複讀)

Isolation.READ_UNCOMMITTED 未提交讀
Isolation.READ_COMMITTED 已提交讀
Isolation.REPEATABLE_READ 重複讀
Isolation.SERIALIZABLE 序列化/不可併發
事務回滾策略

【rollbackFor屬性】
當發生什麼樣的異常(包括其子類)時,進行回滾

對於Spring框架,默認情況下,RuntimeException類型纔會回滾;對於編譯時異常和Error,是不會回滾的
所以,需要修改Spring的默認回滾策略

例如:rollbackFor = Exception.class

Tips:
⑴ RuntimeException:運行時異常
例如FileNotFoundException,這種可以遇見到的異常

⑵ Exception:編譯期異常/檢查異常/受控異常

與rollbackFor相對的是:
【noRollbackFor】屬性
遇到指定的異常類型(包括其子類),不進行回滾
事務超時屬性

【timeout】屬性
timeout = 超時時間(秒)

如果一個事務的執行時間,超過了指定的時長(n秒),就被視爲超時。當該事務執行完畢時,會拋出異常:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was ???
只讀屬性

【readOnly】屬性
一般查詢的時候,會將該屬性設置爲true,表明爲只讀。這樣數據庫底層會對查詢進行優化處理,提高查詢的效率

注意:當一個連接對象(Connection)設置了只讀屬性,則其再操作增刪改時,會報錯:
java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
Transaction屬性設置示例

【示例一】

// 事務傳播行爲爲當有事務時就加入,沒有就新建一個事務;
// 默認隔離級別;
// 事務回滾策略:Exception及其子異常
// 超時:3秒超時
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
rollbackFor = Exception.class,
timeout = 3)
public void testTransaction() { }

【示例二】

// 查詢業務操作,設置連接屬性爲只讀,提高效率
@Transactional(readOnly = true)
public void testQuery() { }

代碼示例

【SQL語句】

USE test;

CREATE TABLE books(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price INT NOT NULL,
stock INT NOT NULL
);

INSERT INTO books(name, price, stock)
VALUES(‘小王子’, 34, 100),
(‘聖經’, 48, 100),
(‘福爾摩斯探案集’, 84, 100);

【Book類(JavaBean)】

屬性:Integer id, String name, Integer price, Integer stock;提供get和set,有參無參構造,重寫toString方法

【核心配置文件】














【BookDao接口】

package com.test.tx.dao;

import java.util.List;

import com.test.tx.bean.Book;

public interface BookDao {

int updateBookPrice(String name, Integer price);

int updateBookStock(String name);

Book getBook(String name);

List<Book> queryBooks();

}

【BookDaoImpl實現類】

package com.test.tx.dao;

import java.util.List;

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

import com.test.tx.bean.Book;

@Repository
public class BookDaoImpl implements BookDao {

@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public int updateBookPrice(String name, Integer price) {
    String sql = "UPDATE test.books SET price = ? WHERE name = ?";
    return jdbcTemplate.update(sql, price, name);
}

@Override
public int updateBookStock(String name) {
    String sql = "UPDATE test.books SET stock = stock - 1 WHERE name = ?";
    return jdbcTemplate.update(sql, name);
}

@Override
public Book getBook(String name) {
    String sql = "SELECT id, name, price, stock FROM test.books WHERE name = ?";
    RowMapper<Book> rowMapper = new BeanPropertyRowMapper<Book>(Book.class);
    return jdbcTemplate.queryForObject(sql, rowMapper, name);
}

@Override
public List<Book> queryBooks() {
    String sql = "SELECT id, name, price, stock FROM test.books";
    RowMapper<Book> rowMapper = new BeanPropertyRowMapper<Book>(Book.class);
    return jdbcTemplate.query(sql, rowMapper);
}

}

【BookService接口】

package com.test.tx.service;

import java.util.List;

import com.test.tx.bean.Book;

public interface BookService {

int updateBookPrice(String name, Integer price) throws Exception;

int updateBookStock(String name) throws Exception;

Book getBook(String name);

List<Book> queryBooks();

}

【BookServiceImpl實現類】

package com.test.tx.service;

import java.util.List;

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

import com.test.tx.bean.Book;
import com.test.tx.dao.BookDao;

@Service
public class BookServiceImpl implements BookService {

@Autowired
private BookDao bookDao;

/*
 * 測試超時
 * 
 * org.springframework.transaction.TransactionTimedOutException: Transaction
 * timed out: deadline was ???
 * 
 * @Transactional(timeout = 3)
 */
// ---------------------------------------------------------------------------------
/*
 * 測試更新時,設置連接爲只讀
 * 
 * Caused by: java.sql.SQLException: Connection is read-only. Queries
 * leading to data modification are not allowed
 * 
 * @Transactional(readOnly = true)
 */
// ---------------------------------------------------------------------------------

// 這裏設置事務的傳播行爲是:不管有沒有事務,都會開啓一個新的事務
// 是爲了測試MulService的testTx方法【因爲和下面的更新圖書庫存操作是兩個事務,所以這個事務出錯了,但是不會回滾。最終效果是價格變了,但庫存沒有變】
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int updateBookPrice(String name, Integer price) throws Exception {
    // 測試超時【讓線程休眠】
    // Thread.sleep(4000);

    return bookDao.updateBookPrice(name, price);
}

/*
 * 如果不設置回滾策略,則會造成執行MulService的testTx方法時,圖書的價格變了,但是庫存沒有變,事務並沒有回滾
 * 
 * @Transactional(rollbackFor = Exception.class)
 */
// ---------------------------------------------------------------------------------

// 這裏設置事務的傳播行爲是:不管有沒有事務,都會開啓一個新的事務
// 是爲了測試MulService的testTx方法【因爲和上面的更新圖書價格操作是兩個事務,所以這個事務出錯了,但是不會回滾。最終效果是價格變了,但庫存沒有變】
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int updateBookStock(String name) throws Exception {
    // 這裏拋出一個編譯時異常【Spring默認是不回滾的】
    // FileInputStream fis = new FileInputStream("a/a/a/a");

    // 這裏有一個算數異常
    int i = 1 / 0;
    return bookDao.updateBookStock(name);
}

// 設置爲只讀的,提高效率
@Transactional(readOnly = true)
public Book getBook(String name) {
    return bookDao.getBook(name);
}

// 設置爲只讀的,提高效率
@Transactional(readOnly = true)
public List<Book> queryBooks() {
    return bookDao.queryBooks();
}

}

【MulService類(用於調用BookServiceImpl的兩個業務方法)】

package com.test.tx.service;

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

@Service
public class MulService {

@Autowired
private BookService bookService;

@Transactional
public void testTx(String name, Integer price) throws Exception {
    bookService.updateBookPrice(name, price);

    bookService.updateBookStock(name);
}

}

【測試類】

package junit.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.tx.bean.Book;
import com.test.tx.service.BookService;
import com.test.tx.service.MulService;

public class TestTx {
private ApplicationContext ioc = new ClassPathXmlApplicationContext(“???.xml”);

@Test
// 測試執行更新圖書價格操作
public void test1() throws Exception {
    BookService bookService = ioc.getBean(BookService.class);
    bookService.updateBookPrice("小王子", 100);
}

@Test
// 測試更新圖書的庫存和價格【兩個Dao操作】
public void test2() throws Exception {
    MulService mulService = ioc.getBean(MulService.class);
    String name = "小王子";
    Integer price = 200;
    mulService.testTx(name, price);
}

@Test
public void test3() {
    BookService bookService = ioc.getBean(BookService.class);
    Book book = bookService.getBook("小王子");
    System.out.println(book);
}

}

7、基於XML的聲明式事務
相關標籤

用於聲明通知的事務標籤。它有tx:attributes子標籤


tx:advice的子標籤,在其裏面 聲明開啓事務的方法標籤

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章