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的子標籤,在其裏面 聲明開啓事務的方法標籤