事務的概念
我們知道,在JavaEE的開發過程中,service方法用於處理主要的業務邏輯,而業務邏輯的處理往往伴隨着對數據庫的多個操作。以我們生活中常見的轉賬為例,service方法要實現將A賬戶轉賬到B賬戶的功能,則該方法內必定要有兩個操作:先將A賬戶的金額減去要轉賬的數目,然后將B賬戶加上相應的金額數目。這兩個操作必定要全部成功,方才表示本次轉賬成功;若有任何一方失敗,則另一方必須回滾(即全部失敗)。事務指的就是這樣一組操作:這組操作是不可分割的,要么全部成功,要么全部失敗
事務的特性
事務具有ACID四個特性:
原子性(Atomicity):事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生
一致性(Consistency):事務在完成后數據的完整性必須保持一致
隔離性(Isolation):多個用戶並發訪問數據庫時,一個用戶的事務不能被其他用戶的事務所干擾,多個並發事務之間的數據要相互隔離
持久性(Durability):一個事務一旦被提交,它對數據庫中數據的改變應該是永久性的,即使數據庫發生故障也不應該對其有任何影響
Spring 事務管理接口
Spring 事務管理為我們提供了三個高層抽象的接口,分別是TransactionProxyFactoryBean,TransactionDefinition,TransactionStatus
1.PlatformTransactionManager事務管理器
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring框架並不直接管理事務,而是通過這個接口為不同的持久層框架提供了不同的PlatformTransactionManager接口實現類,也就是將事務管理的職責委托給Hibernate或者iBatis等持久化框架的事務來實現
org.springframework.jdbc.datasource.DataSourceTransactionManager:使用JDBC或者iBatis進行持久化數據時使用
org.springframework.orm.hibernate5.HibernateTransactionManager:使用hibernate5版本進行持久化數據時使用
org.springframework.orm.jpa.JpaTransactionManager:使用JPA進行持久化數據時使用
org.springframework.jdo.JdoTransactionManager:當持久化機制是jdo時使用
org.springframework.transaction.jta.JtaTransactionManager:使用一個JTA實現來管理事務,在一個事務跨越多個資源時必須使用
PlatformTransactionManager接口源碼:
public interface PlatformTransactionManager {
//事務管理器通過TransactionDefinition,獲得“事務狀態”,從而管理事務
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//根據狀態提交
void commit(TransactionStatus var1) throws TransactionException;
//根據狀態回滾
void rollback(TransactionStatus var1) throws TransactionException;
}
2.TransactionDefinition定義事務基本屬性
org.springframework.transaction.TransactionDefinition接口用於定義一個事務,它定義了Spring事務管理的五大屬性:隔離級別、傳播行為、是否只讀、事務超時、回滾規則
2.1隔離級別
什么是事務的隔離級別?我們知道,隔離性是事務的四大特性之一,表示多個並發事務之間的數據要相互隔離,隔離級別就是用來描述並發事務之間隔離程度的大小
在並發事務之間如果不考慮隔離性,會引發如下安全性問題:
臟讀 :一個事務讀到了另一個事務的未提交的數據
不可重復讀 :一個事務讀到了另一個事務已經提交的 update 的數據導致多次查詢結果不一致
幻讀 :一個事務讀到了另一個事務已經提交的 insert 的數據導致多次查詢結果不一致
在 Spring 事務管理中,為我們定義了如下的隔離級別:
ISOLATION_DEFAULT:使用數據庫默認的隔離級別
ISOLATION_READ_UNCOMMITTED:最低的隔離級別,允許讀取已改變而沒有提交的數據,可能會導致臟讀、幻讀或不可重復讀
ISOLATION_READ_COMMITTED:允許讀取事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生
ISOLATION_REPEATABLE_READ:對同一字段的多次讀取結果都是一致的,除非數據事務本身改變,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生
ISOLATION_SERIALIZABLE:最高的隔離級別,完全服從ACID的隔離級別,確保不發生臟讀、不可重復讀以及幻讀,也是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的數據庫表來實現的
2.2傳播行為
Spring事務傳播機制規定了事務方法和事務方法發生嵌套調用時事務如何進行傳播,即協調已經有事務標識的方法之間的發生調用時的事務上下文的規則
Spring定義了七種傳播行為,這里以方法A和方法B發生嵌套調用時如何傳播事務為例說明:
PROPAGATION_REQUIRED:A如果有事務,B將使用該事務;如果A沒有事務,B將創建一個新的事務
PROPAGATION_SUPPORTS:A如果有事務,B將使用該事務;如果A沒有事務,B將以非事務執行
PROPAGATION_MANDATORY:A如果有事務,B將使用該事務;如果A沒有事務,B將拋異常
PROPAGATION_REQUIRES_NEW:A如果有事務,將A的事務掛起,B創建一個新的事務;如果A沒有事務,B創建一個新的事務
PROPAGATION_NOT_SUPPORTED:A如果有事務,將A的事務掛起,B將以非事務執行;如果A沒有事務,B將以非事務執行
PROPAGATION_NEVER:A如果有事務,B將拋異常;A如果沒有事務,B將以非事務執行
PROPAGATION_NESTED:A和B底層采用保存點機制,形成嵌套事務
2.3是否只讀
如果將事務設置為只讀,表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務
2.4事務超時
事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那么就會自動回滾,而不是一直等待其結束。在 TransactionDefinition 中以 int 的值來表示超時時間,默認值是-1,其單位是秒
2.5回滾規則
回滾規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時才會回滾
3.TransactionStatus事務狀態
org.springframework.transaction.TransactionStatus接口用來記錄事務的狀態,該接口定義了一組方法,用來獲取或判斷事務的相應狀態信息
TransactionStatus接口源碼:
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();// 是否是新的事物
boolean hasSavepoint();// 是否有恢復點
void setRollbackOnly();// 設置為只回滾
boolean isRollbackOnly();// 是否為只回滾
void flush();// 刷新
boolean isCompleted();// 是否已完成
}
Spring 事務管理實現方式
Spring 事務管理有兩種方式:編程式事務管理、聲明式事務管理
編程式事務管理通過TransactionTemplate手動管理事務,在實際應用中很少使用,我們來重點學習聲明式事務管理
聲明式事務管理有三種實現方式:基於TransactionProxyFactoryBean的方式、基於AspectJ的XML方式、基於注解的方式
我們以用戶轉賬為例來學習這三種不同的實現方式,首先來搭建轉賬環境
1.建表,初始化數據庫
create table account
(
id bigint auto_increment primary key,
name varchar(32) not null,
money bigint not null,
constraint account_name_uindex
unique (name)
);
insert into account (name, money) values('Bill', 2000),('Jack', 2000);
數據庫原始數據:

2.創建DAO實現類
public class TransferDaoImpl extends JdbcDaoSupport implements TransferDao {
/**
* @param name 賬戶名稱
* @param amount 支出金額
*/
@Override
public void payMoney(String name, Long amount) {
String sql = "update account set money=money-? where name=?";
this.getJdbcTemplate().update(sql, amount, name);
}
/**
* @param name 賬戶名稱
* @param amount 收入金額
*/
@Override
public void collectMoney(String name, Long amount) {
String sql = "update account set money=money+? where name=?";
this.getJdbcTemplate().update(sql, amount, name);
}
}
3.創建Service實現類(事務管理類)
public class TransferServiceImpl implements TransferService {
private TransferDao transferDao;
public void setTransferDao(TransferDao transferDao) {
this.transferDao = transferDao;
}
/**
* @param source 支出方賬戶名稱
* @param name 收入方賬戶名稱
* @param amount 轉賬金額
*/
@Override
public void transferMoney(String source, String destination, Long amount) {
transferDao.payMoney(source, amount);
int i = 100/0;//此處用於測試拋異常時是否會回滾
transferDao.collectMoney(destination, amount);
}
}
4.創建Spring核心配置文件
<!-- 讀取db.properties配置信息 -->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!-- 配置c3p0數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driverClass}" />
<property name="jdbcUrl" value="${db.url}" />
<property name="user" value="${db.username}" />
<property name="password" value="${db.password}" />
</bean>
<bean id="transferDao" class="com.tx.dao.impl.TransferDaoImpl">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transferService" class="com.tx.service.impl.TransferServiceImpl">
<property name="transferDao" ref="transferDao" />
</bean>
基於TransactionProxyFactoryBean的方式
在spring核心配置文件中添加事務管理器的配置和TransactionProxyFactoryBean代理對象
<!--配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置業務層的代理-->
<bean id="transferServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--配置目標對象-->
<property name="target" ref="transferService" />
<!--注入事務管理器-->
<property name="transactionManager" ref="transactionManager" />
<!--注入事務屬性-->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事務的傳播行為
* ISOLATION :事務的隔離級別
* readOnly :是否只讀
* -Exception :發生哪些異常回滾事務
* +Exception :發生哪些異常不回滾事務
-->
<prop key="transfer*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
測試代碼:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringTransactionApplicationTests {
@Autowired
TransferService transferService;
@Resource(name="transferServiceProxy")
TransferService transferServiceProxy;
@Test
public void contextLoads() {
//注意,此處引入的是代理對象transferServiceProxy,而不是transferService
transferServiceProxy.transferMoney("Bill","Jack", 200L);
}
}
運行結果:
java.lang.ArithmeticException: / by zero
at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:22)
at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)
執行service事務方法時拋出異常,事務回滾,數據庫中數據未發生改變

基於AspectJ的XML方式
在spring核心配置文件中添加事務管理器的配置、事務的增強以及切面
<!--配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置事務的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<aop:pointcut id="pointcut1" expression="execution(* com.tx.service.impl.*ServiceImpl.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1" />
</aop:config>
測試代碼:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringTransactionApplicationTests {
@Autowired
TransferService transferService;
@Test
public void contextLoads() {
transferService.transferMoney("Bill","Jack", 200L);
}
}
運行結果:
java.lang.ArithmeticException: / by zero
at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:22)
at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)
執行service事務方法時拋出異常,事務回滾,數據庫中數據未發生改變

基於注解的方式
在spring核心配置文件中添加事務管理器的配置和開啟事務注解
<!--配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--開啟事務注解-->
<tx:annotation-driven transaction-manager="transactionManager" />
在事務方法中添加@Transaction注解
@Transactional
public void transferMoney(String source, String destination, Long amount) {
transferDao.payMoney(source, amount);
int i = 100/0;
transferDao.collectMoney(destination, amount);
}
測試代碼:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringTransactionApplicationTests {
@Autowired
TransferService transferService;
@Test
public void contextLoads() {
transferService.transferMoney("Bill","Jack", 200L);
}
}
運行結果:
java.lang.ArithmeticException: / by zero
at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:24)
at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)
數據庫中數據位發生任何改變

總結
在聲明式事務管理的三種實現方式中,基於TransactionProxyFactoryBean的方式需要為每個進行事務管理的類配置一個TransactionProxyFactoryBean對象進行增強,所以開發中很少使用;基於AspectJ的XML方式一旦在XML文件中配置好后,不需要修改源代碼,所以開發中經常使用;基於注解的方式開發較為簡單,配置好后只需要在事務類上或方法上添加@Transaction注解即可,所以開發中也經常使用
