事務(wù)
事務(wù)定義
事務(wù)是正確執(zhí)行一系列的操作(或動作),使得數(shù)據(jù)庫從一種狀態(tài)轉(zhuǎn)換成另一種狀態(tài),且保證操作全部成功,或者全部失敗。我們知道在JAVAEE的開發(fā)過程中,service層的方法通常用于業(yè)務(wù)邏輯處理,而業(yè)務(wù)往往涉及到對數(shù)據(jù)庫的多個操作。
以生活中常見的轉(zhuǎn)賬為例,假設(shè)某個方法要實現(xiàn)將A賬戶轉(zhuǎn)賬到B賬戶的功能,則該方法內(nèi)必定有兩個操作:先將A賬戶的金額減去要轉(zhuǎn)賬的數(shù)目,然后將B賬戶加上相應(yīng)的金額數(shù)目,這個方法看似簡單,但是需要這兩個操作要么全部成功(表示本次轉(zhuǎn)賬成功);若有任意一方失敗,則另一方必須回滾(即必須回到初始狀態(tài),全部失敗)。所以上面介紹的事務(wù)其實就是指:一組操作是不可分割的,要么全部成功,要么全部失敗。
事務(wù)的ACID四個特性
我們知道事務(wù)具有ACID四個特性:原子性、一致性、隔離性和持久性。
原子性(Atomicity):事務(wù)是一個不可分割的工作單位,事務(wù)中的操作要么都發(fā)生,要么都不發(fā)生;一致性(Consistency):事務(wù)在完成后數(shù)據(jù)的完整性必須保持一致;隔離性(Isolation):多個用戶并發(fā)訪問數(shù)據(jù)庫時,一個用戶的事務(wù)不能被其他用戶的事務(wù)所干擾,多個并發(fā)事務(wù)之間的數(shù)據(jù)要相互隔離;持久性(Durability):一個事務(wù)一旦被提交,它對數(shù)據(jù)庫中數(shù)據(jù)的改變應(yīng)該是永久性的,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響。
Java事務(wù)
Java事務(wù)其實是以Java編寫的程序或系統(tǒng),用于實現(xiàn)ACID的操作。而Java事務(wù)的實現(xiàn)是通過JDBC提供的相應(yīng)方法來間接實現(xiàn)對數(shù)據(jù)庫的增刪改查操作,通過事務(wù)轉(zhuǎn)移到Java程序代碼中進(jìn)行控制。
需要注意的是必須確保事務(wù)要么全部執(zhí)行成功,要么撤銷不執(zhí)行。因此可以這么說Java事務(wù)機(jī)制和原理就是操作確保數(shù)據(jù)庫操作的ACID特性。
Java事務(wù)的類型分為三種:JDBC事務(wù)、JTA(Java Transaction API)事務(wù)和容器事務(wù)。其中JDBC事務(wù)是用Connection對象控制的手動模式和自動模式;JTA事務(wù)是與實現(xiàn)無關(guān)的,與協(xié)議無關(guān)的API;容器事務(wù)是應(yīng)用服務(wù)器提供的,且大多數(shù)是基于JTA完成(通常是基于JNDI(Java Naming and Directory Interface,Java命名和目錄接口)的,一種非常復(fù)雜的API實現(xiàn))。
三種事務(wù)的差異:JDBC事務(wù),它控制的局限性在一個數(shù)據(jù)庫連接內(nèi),但是其使用較為簡單。JTA(Java Transaction API)事務(wù),它功能強(qiáng)大,可跨越多個數(shù)據(jù)庫或者多個DAO,但是使用起來較為復(fù)雜;容器事務(wù),主要指的是J2EE應(yīng)用服務(wù)器提供的事務(wù)管理,局限于EJB應(yīng)用使用。
Spring事務(wù)管理接口
先來看一張圖,該圖反映了事務(wù)在Spring中占據(jù)著較為核心的位置:
而下面這張圖則是Spring事務(wù)管理提供的三個高層抽象接口,分別是TransactionProxyFactoryBean,TransactionDefinition,TransactionStatus
PlatformTransactionManager事務(wù)管理器
Spring事務(wù)管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring框架并不直接管理事務(wù),而是通過該接口為不同的持久層框架提供了不同的PlatformTransactionManager接口實現(xiàn)類,也就是將事務(wù)管理的職責(zé)委托給Hibernate或者M(jìn)ybatis等持久層框架的事務(wù)來實現(xiàn)。
org.springframework.jdbc.datasource.DataSourceTransactionManager:使用JDBC或者M(jìn)ybatis進(jìn)行持久化數(shù)據(jù)時使用,通過調(diào)用java.sql.Connection來管理事務(wù)。
org.springframework.orm.hibernate5.HibernateTransactionManager:使用hibernate5版本進(jìn)行持久化數(shù)據(jù)時使用,將事務(wù)管理的職責(zé)委托給org.hibernate.Transaction對象來管理事務(wù),而后者是從Hibernate Session中獲取到的。
org.springframework.orm.jpa.JpaTransactionManager:使用JPA進(jìn)行持久化數(shù)據(jù)時使用,通過一個JPA實體管理工廠(javax.persitence.EntityManagerFactory接口的任意實現(xiàn))將與工廠所產(chǎn)生的JPA EntityManager合作來構(gòu)建事務(wù)。
org.springframework.jdo.JdoTransactionManager:當(dāng)持久化機(jī)制是jdo時使用。
org.springframework.transaction.jta.JtaTransactionManager:使用一個JTA實現(xiàn)來管理事務(wù),在一個事務(wù)跨越多個資源時必須使用,它將事務(wù)管理的職責(zé)委托給javax.transaction.UserTransaction和javax.transaction.TransactionManager對象來管理事務(wù)。
PlatformTransactionManager接口源碼:
public interface PlatformTransactionManager {
//事務(wù)管理器通過TransactionDefinition,獲得“事務(wù)狀態(tài)”,從而管理事務(wù)
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//根據(jù)狀態(tài)提交
void commit(TransactionStatus var1) throws TransactionException;
//根據(jù)狀態(tài)回滾
void rollback(TransactionStatus var1) throws TransactionException;
}
TransactionDefinition定義事務(wù)基本屬性
org.springframework.transaction.TransactionDefinition接口用于定義一個事務(wù),它定義了Spring事務(wù)管理的五大屬性:隔離級別、傳播行為、是否只讀、事務(wù)超時、回滾規(guī)則。
隔離級別
什么是事務(wù)的隔離級別?我們知道隔離性是事務(wù)的四大特性之一,表示多個并發(fā)事務(wù)之間的數(shù)據(jù)要相互隔離,隔離級別就是用于描述并發(fā)事務(wù)之間隔離程度的大小。
如果在并發(fā)事務(wù)之間如果不考慮隔離性,會引發(fā)一些安全性問題:臟讀、不可重復(fù)讀和幻讀等。
臟讀是指一個事務(wù)讀到了另一個事務(wù)的未提交的數(shù)據(jù);不可重復(fù)讀是指一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的修改的數(shù)據(jù)導(dǎo)致多次查詢結(jié)果不一致;幻讀是指一個事務(wù)讀到了另一個事務(wù)已經(jīng)提交的插入的數(shù)據(jù)導(dǎo)致多次查詢結(jié)果不一致。
在Spring事務(wù)管理中,定義了如下5種隔離級別:
ISOLATION_DEFAULT:使用數(shù)據(jù)庫默認(rèn)的隔離級別;ISOLATION_READ_UNCOMMITTED:最低的隔離級別,允許讀取已改變而沒有提交的數(shù)據(jù),可能會導(dǎo)致臟讀、幻讀或不可重復(fù)讀;ISOLATION_READ_COMMITTED:允許讀取事務(wù)已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生;ISOLATION_REPEATABLE_READ:對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)事務(wù)本身改變,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生;ISOLATION_SERIALIZABLE:最高的隔離級別,完全服從ACID的隔離級別,確保不發(fā)生臟讀、不可重復(fù)讀以及幻讀,也是最慢的事務(wù)隔離級別,因為它通常是通過完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫表來實現(xiàn)的。
傳播行為
Spring事務(wù)傳播機(jī)制規(guī)定了事務(wù)方法和事務(wù)方法發(fā)生嵌套調(diào)用時事務(wù)如何進(jìn)行傳播,即協(xié)調(diào)已經(jīng)有事務(wù)標(biāo)識的方法之間的發(fā)生調(diào)用時的事務(wù)上下文的規(guī)則。Spring定義了七種傳播行為,這里以方法A和方法B發(fā)生嵌套調(diào)用時如何傳播事務(wù)為例說明:
PROPAGATION_REQUIRED:A如果有事務(wù),B將使用該事務(wù);如果A沒有事務(wù),B將創(chuàng)建一個新的事務(wù);PROPAGATION_SUPPORTS:A如果有事務(wù),B將使用該事務(wù);如果A沒有事務(wù),B將以非事務(wù)執(zhí)行;PROPAGATION_MANDATORY:A如果有事務(wù),B將使用該事務(wù);如果A沒有事務(wù),B將拋異常;PROPAGATION_REQUIRES_NEW:A如果有事務(wù),將A的事務(wù)掛起,B創(chuàng)建一個新的事務(wù);如果A沒有事務(wù),B創(chuàng)建一個新的事務(wù);PROPAGATION_NOT_SUPPORTED:A如果有事務(wù),將A的事務(wù)掛起,B將以非事務(wù)執(zhí)行;如果A沒有事務(wù),B將以非事務(wù)執(zhí)行;PROPAGATION_NEVER:A如果有事務(wù),B將拋異常;A如果沒有事務(wù),B將以非事務(wù)執(zhí)行;PROPAGATION_NESTED:A和B底層采用保存點機(jī)制,形成嵌套事務(wù)。
總結(jié)起來就是這張圖:
是否只讀
如果將事務(wù)設(shè)置為只讀,表示這個事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù), 這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務(wù)。
注意事務(wù)的是否“只讀”屬性,不同的數(shù)據(jù)庫廠商支持不同,需要結(jié)合數(shù)據(jù)庫廠商的具體說明,如Oracle的"readOnly"不起作用,不影響其增刪改查;MySQL的"readOnly"為true時,只能查,增刪改都會拋出異常。
事務(wù)超時
事務(wù)超時就是事務(wù)的一個定時器,在特定時間內(nèi)事務(wù)如果沒有執(zhí)行完畢,那么就會自動回滾,而不是一直等待其結(jié)束。在TransactionDefinition中以int的值來表示超時時間,默認(rèn)值是-1(單位是秒)。
設(shè)計事務(wù)時應(yīng)當(dāng)注意,為使應(yīng)用程序很好地運(yùn)行,事務(wù)不能運(yùn)行太長時間,因為事務(wù)可能涉及對后端數(shù)據(jù)庫的鎖定,因此長時間的事務(wù)會不必要的占用數(shù)據(jù)庫資源。
回滾規(guī)則
回滾規(guī)則定義了哪些異常會導(dǎo)致事務(wù)回滾而哪些不會。默認(rèn)情況下,事務(wù)只有遇到運(yùn)行期異常時才會回滾,而在遇到檢查型異常時不會回滾。
如果你需要自定義回滾,可以按照如下的策略進(jìn)行:1、聲明事務(wù)在遇到特定的檢查型異常時,像遇到運(yùn)行期異常那樣回滾;2、聲明事務(wù)遇到特定的異常不回滾,即使這些異常是運(yùn)行期異常。
TransactionStatus事務(wù)狀態(tài)
org.springframework.transaction.TransactionStatus接口用來記錄事務(wù)的狀態(tài),該接口定義了一組方法,用來獲取或判斷事務(wù)的相應(yīng)狀態(tài)信息。TransactionStatus接口源碼如下:
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();// 是否是新的事物
boolean hasSavepoint();// 是否有恢復(fù)點
void setRollbackOnly();// 設(shè)置為只回滾
boolean isRollbackOnly();// 是否為只回滾
void flush();// 刷新
boolean isCompleted();// 是否已完成
}
Spring事務(wù)管理實現(xiàn)方式
Spring事務(wù)管理有兩種方式:編程式事務(wù)管理和聲明式事務(wù)管理。
編程式事務(wù)管理
編程式事務(wù)管理通過TransactionTemplate手動管理事務(wù),在實際應(yīng)用中很少使用,但是可以了解一下。
編程式事務(wù)管理實現(xiàn)方式通常有兩種:事務(wù)管理器方式和模板事務(wù)方式。
事務(wù)管理器(Platform Transaction Manaager)方式,類似應(yīng)用JTA UserTransaction API方式,但異常處理更簡潔;其核心類為:Spring事務(wù)管理的三個接口類以及Jdbc Template類。
模板事務(wù)(Transaction Template)方式,此為Spring官方團(tuán)隊推薦的編程式事務(wù)管理方式;主要工具為Jdbc Template類。
聲明式事務(wù)管理
聲明式事務(wù)管理在實際開發(fā)中非常常見,因此需要仔細(xì)學(xué)習(xí)。聲明式事務(wù)管理基于AOP模式,對方法進(jìn)行前后攔截。
聲明式事務(wù)管理的配置類型有5種,但是由于3中在Spring2.0后不推薦使用,因此這里主要就是兩種:tx攔截器和全注釋。
聲明式事務(wù)管理有三種實現(xiàn)方式:基于TransactionProxyFactoryBean的方式、基于AspectJ的XML方式、基于注解的方式。接下來將以用戶轉(zhuǎn)賬為例來學(xué)習(xí)這三種不同的實現(xiàn)方式。
第一步,新建數(shù)據(jù)庫spring_money和表account:
drop database if exists spring_money;
create database spring_money;
use spring_money;
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('小明', 2000),('小白', 2000);
第二步,新建Maven項目spring_money:其中pom.xml配置信息為:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.Apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.envy</groupId>
<artifactId>Spring_money</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.version>4.2.7.RELEASE</spring.version>
</properties>
<dependencies>
<!--Spring核心組件-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</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-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Spring AOP組件-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!--AspectJ AOP開發(fā)需要引入的兩個包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!--MySql驅(qū)動,注意版本號-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--JDBC Template-->
<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>
<!-- druid數(shù)據(jù)源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.25</version>
</dependency>
<!-- 測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
第三步,創(chuàng)建一個實體包com.envy.entity,接著在里面新建Account實體類:
package com.envy.entity;
public class Account {
private int id;
private String name;
private Long money;
public int getId(){
return id;
}
public void setId(int id){
this.id=id;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public Long getMoney(){
return money;
}
public void setMoney(Long money){
this.money = money;
}
public String toString(){
return "Account:id is"+id+";name is"+ name+ ";money is"+money;
}
}
第四步,創(chuàng)建一個Dao包com.envy.dao,接著在里面新建TransferDao接口:
package com.envy.dao;
public interface TransferDao {
//付款,name賬戶名稱,amount支出金額
void payMoney(String name,Long amount);
//收款,name賬戶名稱,amount收到金額
void collectMoney(String name,Long amount);
}
然后在com.envy.dao中新建一個Impl包,在Impl中新建TransferDao接口的實現(xiàn)類TransferDaoImpl:
package com.envy.dao.Impl;
import com.envy.dao.TransferDao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class TransferDaoImpl extends JdbcDaoSupport implements TransferDao {
//付款,name賬戶名稱,amount支出金額
public void payMoney(String name, Long amount) {
String sql = "update account set money = money-amount where name=?";
this.getJdbcTemplate().update(sql,amount,name);
}
//收款,name賬戶名稱,amount收到金額
public void collectMoney(String name, Long amount) {
String sql = "update account set money = money+amount where name=?";
this.getJdbcTemplate().update(sql,amount,name);
}
}
第五步,創(chuàng)建一個service包com.envy.service,接著在里面新建TransferService接口:
package com.envy.service;
public interface TransferService {
void transferMoney(String source,String destination, Long amount);
}
然后在com.envy.service中新建一個Impl包,在Impl中新建TransferService接口的實現(xiàn)類TransferServiceImpl:
package com.envy.service.Impl;
import com.envy.dao.TransferDao;
import com.envy.service.TransferService;
public class TransferServiceImpl implements TransferService {
private TransferDao transferDao;
public void setTransferDao(TransferDao transferDao){
this.transferDao=transferDao;
}
//轉(zhuǎn)賬操作,source支出方賬戶,destination收款方賬戶,amount轉(zhuǎn)賬金額
public void transferMoney(String source, String destination, Long amount) {
//付款操作
transferDao.payMoney(source,amount);
int i = 100/0;//此處用于測試拋異常時是否會回滾
//收款操作
transferDao.collectMoney(destination,amount);
}
}
第六步,創(chuàng)建一個db.properties配置文件:
#加載驅(qū)動
druid.driverClassName=com.mysql.jdbc.Driver
#加載數(shù)據(jù)庫
druid.url=jdbc:mysql://localhost:3306/spring_money?useUnicode=true&characterEncoding=UTF-8
#用戶名
druid.username=root
#密碼
druid.password=root
第七步,創(chuàng)建一個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:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--不使用外部db.properties配置文件-->
<!--配置數(shù)據(jù)源-->
<!--<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">-->
<!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>-->
<!--<property name="url" value="jdbc:mysql://127.0.0.1:3306/selection_course?useUnicode=true&characterEncoding=utf-8"/>-->
<!--<property name="username" value="root"/>-->
<!--<property name="password" value="envy123"/>-->
<!--</bean>-->
<!--讀取外部db.properties配置信息-->
<context:property-placeholder location="classpath:db.properties"/>
<!--開啟包掃描-->
<context:component-scan base-package="com.envy.service.Impl"/>
<!--配置數(shù)據(jù)源-->
<bean id = "dataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-method = "close">
<!--數(shù)據(jù)庫基本信息配置-->
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.username}"/>
<property name="password" value="${druid.password}"/>
<property name="driverClassName" value="${druid.driverClassName}"/>
</bean>
<bean id="transferDao" class="com.envy.dao.Impl.TransferDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transferService" class="com.envy.service.Impl.TransferServiceImpl">
<property name="transferDao" ref="transferDao"/>
</bean>
</beans>
第八步,按照前面所述3種方式開始測試,注意每次測試均從此處開始。
基于TransactionProxyFactoryBean的方式
首先在applicationContext.xml文件中添加事務(wù)管理器相關(guān)配置和TransactionProxyFactoryBean代理對象(前七步的代碼這里不再重復(fù)貼出,這里這貼出與本方式相關(guān)的代碼):
<!--基于TransactionProxyFactoryBean的方式-->
<!--配置事務(wù)管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置業(yè)務(wù)層的代理-->
<bean id="transferServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--配置目標(biāo)對象-->
<property name="target" ref="transferService" />
<!--注入事務(wù)管理器-->
<property name="transactionManager" ref="transactionManager" />
<!--注入事務(wù)屬性-->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事務(wù)的傳播行為
* ISOLATION :事務(wù)的隔離級別
* readOnly :是否只讀
* -Exception :發(fā)生哪些異常回滾事務(wù)
* +Exception :發(fā)生哪些異常不回滾事務(wù)
-->
<prop key="transfer*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
接著新建一個測試類SpringTransactionApplicationTest:
import com.envy.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTransactionApplicationTest {
@Autowired
private TransferService transferService;
@Resource(name="transferServiceProxy")
private TransferService transferServiceProxy;
@Test
public void TestOne(){
//注意,此處使用的是代理對象transferServiceProxy,而不是transferService
transferServiceProxy.transferMoney("小明","小白",66L);
}
}
運(yùn)行結(jié)果為:
java.lang.ArithmeticException: / by zero
可以看到執(zhí)行service事務(wù)方法時拋出異常,事務(wù)回滾,數(shù)據(jù)庫中數(shù)據(jù)未發(fā)生改變:(如果將transferMoney方法中的int i = 100/0;代碼注釋掉,則轉(zhuǎn)賬會成功。)
基于AspectJ的XML方式
首先在applicationContext.xml文件中添加事務(wù)管理器的配置、事務(wù)的增強(qiáng)以及切面:
<!--基于AspectJ的XML方式-->
<!--配置事務(wù)管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事務(wù)的通知-->
<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.envy.service.Impl.*ServiceImpl.*(..))"/>
<!--配置AOP的切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
接著新建一個測試類SpringTransactionApplicationTestTwo:
import com.envy.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTransactionApplicationTestTwo {
@Autowired
private TransferService transferService;
@Test
public void TestTwo(){
transferService.transferMoney("小明","小白",88L);
}
}
運(yùn)行結(jié)果為:
java.lang.ArithmeticException: / by zero
可以看到執(zhí)行service事務(wù)方法時拋出異常,事務(wù)回滾,數(shù)據(jù)庫中數(shù)據(jù)未發(fā)生改變:
如果將transferMoney方法中的int i = 100/0;代碼注釋掉,則轉(zhuǎn)賬會成功:
基于注解的方式
首先在applicationContext.xml文件中添加事務(wù)管理器的配置和開啟事務(wù)注解:
<!--基于注解的方式-->
<!--配置事務(wù)管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--開啟事務(wù)注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
接著在TransferServiceImpl類中的事務(wù)方法transferMoney中添加@Transactional注解:
@Transactional
public void transferMoney(String source, String destination, Long amount) {
//付款操作
transferDao.payMoney(source,amount);
// int i = 100/0;//此處用于測試拋異常時是否會回滾
//收款操作
transferDao.collectMoney(destination,amount);
}
最后新建一個測試類SpringTransactionApplicationTestThree:
import com.envy.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTransactionApplicationTestThree {
@Autowired
private TransferService transferService;
@Test
public void TestThree(){
transferService.transferMoney("小明","小白",99L);
}
}
運(yùn)行結(jié)果為:
java.lang.ArithmeticException: / by zero
可以看到執(zhí)行service事務(wù)方法時拋出異常,事務(wù)回滾,數(shù)據(jù)庫中數(shù)據(jù)未發(fā)生改變:
如果將transferMoney方法中的int i = 100/0;代碼注釋掉,則轉(zhuǎn)賬會成功:
編程式事務(wù)管理和聲明式事務(wù)管理區(qū)別
編程式事務(wù)管理允許用戶在代碼中精確定義事務(wù)的邊界;聲明式事務(wù)管理有助于用戶將操作與事務(wù)規(guī)則進(jìn)行解耦(基于AOP交由Spring容器來實現(xiàn),實現(xiàn)關(guān)注點聚焦在業(yè)務(wù)邏輯上)。
概括來說,編程式事務(wù)管理侵入到了業(yè)務(wù)代碼里面,但是提供了更加詳細(xì)的事務(wù)管理,而聲明式編程由于基于AOP,所以既能起到事務(wù)管理的作用,又可以不影響業(yè)務(wù)代碼的具體實現(xiàn)。
那么如何選擇編程式事務(wù)管理和聲明式事務(wù)管理呢?小型應(yīng)用,事務(wù)操作少。建議使用編程式事務(wù)管理來實現(xiàn)Transaction Template,因為它簡單、顯示操作、直觀明顯、可以設(shè)置事務(wù)的名稱。
對于那些大型應(yīng)用,事務(wù)操作多。建議使用聲明式事務(wù)管理來實現(xiàn),因為它業(yè)務(wù)復(fù)雜度高、關(guān)聯(lián)性緊密,關(guān)注點聚焦到業(yè)務(wù)層面,實現(xiàn)了業(yè)務(wù)和事務(wù)的解耦。
關(guān)于事務(wù)管理器的選擇,需要基于不同的數(shù)據(jù)源來選擇相對應(yīng)的事務(wù)管理器,并選擇正確的Platform TransactionManager實現(xiàn)類,而全局事務(wù)的選擇可以使用JtaTransactionManager。
聲明式事務(wù)管理的三種實現(xiàn)方式總結(jié)
在聲明式事務(wù)管理的三種實現(xiàn)方式中,基于TransactionProxyFactoryBean的方式需要為每個進(jìn)行事務(wù)管理的類配置一個TransactionProxyFactoryBean對象進(jìn)行增強(qiáng),所以開發(fā)中很少使用;基于AspectJ的XML方式一旦在XML文件中配置后,不需要修改源代碼,所以開發(fā)中經(jīng)常使用;基于注解的方式開發(fā)較為簡單,配置后只需要在事務(wù)類上或方法上添加@Transactiona注解即可,所以開發(fā)中也經(jīng)常使用。






