什么是事務?
數(shù)據(jù)庫事務(簡稱:事務)是數(shù)據(jù)庫管理系統(tǒng)執(zhí)行過程中的一個邏輯單位束倍,由一個有限的數(shù)據(jù)庫操作序列構成。
事務應該具有4個屬性:原子性盟戏、一致性绪妹、隔離性、持久性柿究,這四個屬性通常稱為ACID特性邮旷。
- 原子性(Atomicity):事務作為一個整體被執(zhí)行,包含在其中的對數(shù)據(jù)庫的操作要么全部被執(zhí)行蝇摸,要么都不執(zhí)行婶肩。
- 一致性(Consistency):事務應確保數(shù)據(jù)庫的狀態(tài)從一個一致狀態(tài)轉變?yōu)榱硪粋€一致狀態(tài)办陷。一致狀態(tài)的含義是數(shù)據(jù)庫中的數(shù)據(jù)應滿足完整性約束。
- 隔離性(Isolation):多個事務并發(fā)執(zhí)行時律歼,一個事務的執(zhí)行不應影響其他事務的執(zhí)行民镜。
- 持久性(Durability):已被提交的事務對數(shù)據(jù)庫的修改應該永久保存在數(shù)據(jù)庫中。
事務的隔離級別
在數(shù)據(jù)庫操作中险毁,為了有效保證并發(fā)讀取數(shù)據(jù)的正確性制圈,提出的事務隔離級別。我們的數(shù)據(jù)庫鎖畔况,也是為了構建這些隔離級別存在的鲸鹦。
隔離級別 | 臟讀(Dirty Read) | 不可重復讀(NonRepeatable Read) | 幻讀(Phantom Read) |
---|---|---|---|
未提交讀(Read uncommitted) | 可能 | 可能 | 可能 |
已提交讀(Read committed) | 不可能 | 可能 | 可能 |
可重復讀(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
說明:
- 未提交讀(Read Uncommitted):允許臟讀,也就是可能讀取到其他會話中未提交事務修改的數(shù)據(jù)
- 提交讀(Read Committed):只能讀取到已經(jīng)提交的數(shù)據(jù)问窃。Oracle等多數(shù)數(shù)據(jù)庫默認都是該級別 (不重復讀)
- 可重復讀(Repeated Read):可重復讀亥鬓。在同一個事務內(nèi)的查詢都是事務開始時刻一致的,InnoDB默認級別域庇。在SQL標準中嵌戈,該隔離級別消除了不可重復讀,但是還存在幻象讀
- 串行讀(Serializable):完全串行化的讀听皿,每次讀都需要獲得表級共享鎖熟呛,讀寫相互都會阻塞
Read Uncommitted這種級別,數(shù)據(jù)庫一般都不會用尉姨,而且任何操作都不會加鎖庵朝,這里就不討論了。
Java中的事務管理
一又厉、編程式事務
在 Spring 出現(xiàn)以前九府,編程式事務管理對開發(fā)者來說是唯一選擇。熟悉Java JDBC 的人都知道覆致,我們需要在代碼中顯式調用Connection 的setAutoCommit()侄旬、commit()、rollback()等事務管理相關的方法煌妈,這就是編程式事務管理儡羔。
1、JDBC 的編程式事務管理
public void save(User user) throws SQLException{
Connection conn = datasource.getConnection();
conn.setAutoCommit(false);
try {
PreparedStatement ps = conn.prepareStatement("insert into user(name,age) value(?,?)");
ps.setString(1,user.getName());
ps.setInt(2,user.getAge());
ps.execute();
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
}finally{
conn.close();
}
}
2璧诵、Hibernate 的編程式事務管理
Hibernate 中需要顯式調用beginTransaction()汰蜘、commit()、rollback()等事務管理相關的方法之宿,如下:
public void save(User user){
Session session = hibernateDao.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.save(user);
tx.commit();
} catch (Exception e) {
if(tx!=null){
tx.rollback();
}
}finally{
session.close();
}
}
3族操、MyBatis 的編程式事務管理
MyBatis中,我們可以通過org.apache.ibatis.session.SqlSession
的commit()比被、rollback() 等事務管理相關的方法坪创,代碼如下:
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = sqlSessionFactory.openSession(false); // 打開會話炕婶,事務開始
try {
IUserMapper mapper = session.getMapper(IUserMapper.class);
User user = new User(9, "Test transaction");
int affectedCount = mapper.updateUser(user); // 因后面的異常而未執(zhí)行commit語句
User user = new User(10, "Test transaction continuously");
int affectedCount2 = mapper.updateUser(user2); // 因后面的異常而未執(zhí)行commit語句
int i = 2 / 0; // 觸發(fā)運行時異常
session.commit(); // 提交會話,即事務提交
} catch (Exception e) {
session.rollback(); //回滾
} finally {
session.close(); // 關閉會話莱预,釋放資源
}
三、Spring 事務管理
1项滑、Spring 的編程式事務管理
基于底層 API 的編程式事務管理
根據(jù)PlatformTransactionManager依沮、TransactionDefinition 和 TransactionStatus 三個核心接口,我們完全可以通過編程的方式來進行事務管理枪狂。代碼如下:
@Service
public class BankServiceImpl implements BankService {
private BankDao bankDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
......
public boolean transfer(Long fromId, Long toId, double amount) {
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean result = false;
try {
result = bankDao.transfer(fromId, toId, amount);
txManager.commit(txStatus);
} catch (Exception e) {
result = false;
txManager.rollback(txStatus);
System.out.println("Transfer Error!");
}
return result;
}
}
基于 TransactionTemplate 的編程式事務管理
2危喉、Spring 的聲明式事務管理
maven依賴:
<properties>
<spring.version>4.3.6.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.21</version>
</dependency>
</dependencies>
spring-dao.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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close" abstract="true" init-method="init" >
<!-- 初始化連接大小 -->
<property name="initialSize" value="2" />
<!-- 連接池最大使用連接數(shù)量 -->
<property name="maxActive" value="10" />
<!-- 連接池最小空閑 -->
<property name="minIdle" value="5" />
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="30000" />
<!-- <property name="poolPreparedStatements" value="true" /> -->
<!-- <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> -->
<property name="validationQuery" value="SELECT 1" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接州疾,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一個連接在池中最小生存的時間辜限,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 打開removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分鐘 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 關閉abanded連接時輸出錯誤日志 -->
<property name="logAbandoned" value="true" />
<!-- 監(jiān)控數(shù)據(jù)庫 -->
<!-- <property name="filters" value="stat" /> -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 配置數(shù)據(jù)源-->
<bean id="masterDataSource" parent="parentDataSource">
<property name="url" value="#{jdbc['master.jdbc.url']}" />
<property name="username" value="#{jdbc['master.jdbc.username']}" />
<property name="password" value="#{jdbc['master.jdbc.password']}" />
<property name="driverClassName" value="#{jdbc['master.jdbc.driver']}" />
<property name="maxActive" value="15" />
</bean>
<!--Spring JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="masterDataSource"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="masterDataSource" />
</bean>
<!-- 啟用事物注解 -->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
以用戶下單為例严蓖,譬如下單分為兩個步驟:更新商品庫存和創(chuàng)建訂單服務兩步薄嫡,代碼如下:
/**
* ${DESCRIPTION}
*
* @author Ricky Fung
* @create 2017-03-03 10:27
*/
@Service("orderBookingService")
public class OrderBookingServiceImpl implements OrderBookingService {
@Autowired
private GoodsItemDao goodsItemDao;
@Autowired
private OrderDao orderDao;
@Transactional(rollbackFor = {BookFailureException.class})
@Override
public boolean book(User user, Order order) throws BookFailureException {
boolean success = true;
int update;
for (OrderItem item : order.getItems()){
update = goodsItemDao.updateStock(item.getId(), item.getQuantity());
if(update<1){
success = false;
break;
}
}
if(success){ //更新庫存成功
update = orderDao.insert(order);
if(update>0){
return true;
}
}
throw new BookFailureException("book failure");
}
}
參考資料
分布式事務系列(1.2)Spring的事務體系:https://yq.aliyun.com/articles/39046?spm=5176.100239.blogcont39044.27.mmXvhw