事務
事務定義
事務是正確執行一系列的操作(或動作),使得數據庫從一種狀態轉換成另一種狀態,且保證操作全部成功,或者全部失敗。我們知道在JAVAEE的開發過程中,service層的方法通常用于業務邏輯處理,而業務往往涉及到對數據庫的多個操作。
以生活中常見的轉賬為例,假設某個方法要實現將A賬戶轉賬到B賬戶的功能,則該方法內必定有兩個操作:先將A賬戶的金額減去要轉賬的數目,然后將B賬戶加上相應的金額數目,這個方法看似簡單,但是需要這兩個操作要么全部成功(表示本次轉賬成功);若有任意一方失敗,則另一方必須回滾(即必須回到初始狀態,全部失敗)。所以上面介紹的事務其實就是指:一組操作是不可分割的,要么全部成功,要么全部失敗。
事務的ACID四個特性
我們知道事務具有ACID四個特性:原子性、一致性、隔離性和持久性。
原子性(Atomicity):事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生;一致性(Consistency):事務在完成后數據的完整性必須保持一致;隔離性(Isolation):多個用戶并發訪問數據庫時,一個用戶的事務不能被其他用戶的事務所干擾,多個并發事務之間的數據要相互隔離;持久性(Durability):一個事務一旦被提交,它對數據庫中數據的改變應該是永久性的,即使數據庫發生故障也不應該對其有任何影響。
Java事務
Java事務其實是以Java編寫的程序或系統,用于實現ACID的操作。而Java事務的實現是通過JDBC提供的相應方法來間接實現對數據庫的增刪改查操作,通過事務轉移到Java程序代碼中進行控制。
需要注意的是必須確保事務要么全部執行成功,要么撤銷不執行。因此可以這么說Java事務機制和原理就是操作確保數據庫操作的ACID特性。
Java事務的類型分為三種:JDBC事務、JTA(Java Transaction API)事務和容器事務。其中JDBC事務是用Connection對象控制的手動模式和自動模式;JTA事務是與實現無關的,與協議無關的API;容器事務是應用服務器提供的,且大多數是基于JTA完成(通常是基于JNDI(Java Naming and Directory Interface,Java命名和目錄接口)的,一種非常復雜的API實現)。
三種事務的差異:JDBC事務,它控制的局限性在一個數據庫連接內,但是其使用較為簡單。JTA(Java Transaction API)事務,它功能強大,可跨越多個數據庫或者多個DAO,但是使用起來較為復雜;容器事務,主要指的是J2EE應用服務器提供的事務管理,局限于EJB應用使用。
Spring事務管理接口
先來看一張圖,該圖反映了事務在Spring中占據著較為核心的位置:
而下面這張圖則是Spring事務管理提供的三個高層抽象接口,分別是TransactionProxyFactoryBean,TransactionDefinition,TransactionStatus
PlatformTransactionManager事務管理器
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring框架并不直接管理事務,而是通過該接口為不同的持久層框架提供了不同的PlatformTransactionManager接口實現類,也就是將事務管理的職責委托給Hibernate或者Mybatis等持久層框架的事務來實現。
org.springframework.jdbc.datasource.DataSourceTransactionManager:使用JDBC或者Mybatis進行持久化數據時使用,通過調用java.sql.Connection來管理事務。
org.springframework.orm.hibernate5.HibernateTransactionManager:使用hibernate5版本進行持久化數據時使用,將事務管理的職責委托給org.hibernate.Transaction對象來管理事務,而后者是從Hibernate Session中獲取到的。
org.springframework.orm.jpa.JpaTransactionManager:使用JPA進行持久化數據時使用,通過一個JPA實體管理工廠(javax.persitence.EntityManagerFactory接口的任意實現)將與工廠所產生的JPA EntityManager合作來構建事務。
org.springframework.jdo.JdoTransactionManager:當持久化機制是jdo時使用。
org.springframework.transaction.jta.JtaTransactionManager:使用一個JTA實現來管理事務,在一個事務跨越多個資源時必須使用,它將事務管理的職責委托給javax.transaction.UserTransaction和javax.transaction.TransactionManager對象來管理事務。
PlatformTransactionManager接口源碼:
public interface PlatformTransactionManager {
//事務管理器通過TransactionDefinition,獲得“事務狀態”,從而管理事務
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//根據狀態提交
void commit(TransactionStatus var1) throws TransactionException;
//根據狀態回滾
void rollback(TransactionStatus var1) throws TransactionException;
}
TransactionDefinition定義事務基本屬性
org.springframework.transaction.TransactionDefinition接口用于定義一個事務,它定義了Spring事務管理的五大屬性:隔離級別、傳播行為、是否只讀、事務超時、回滾規則。
隔離級別
什么是事務的隔離級別?我們知道隔離性是事務的四大特性之一,表示多個并發事務之間的數據要相互隔離,隔離級別就是用于描述并發事務之間隔離程度的大小。
如果在并發事務之間如果不考慮隔離性,會引發一些安全性問題:臟讀、不可重復讀和幻讀等。
臟讀是指一個事務讀到了另一個事務的未提交的數據;不可重復讀是指一個事務讀到了另一個事務已經提交的修改的數據導致多次查詢結果不一致;幻讀是指一個事務讀到了另一個事務已經提交的插入的數據導致多次查詢結果不一致。
在Spring事務管理中,定義了如下5種隔離級別:
ISOLATION_DEFAULT:使用數據庫默認的隔離級別;ISOLATION_READ_UNCOMMITTED:最低的隔離級別,允許讀取已改變而沒有提交的數據,可能會導致臟讀、幻讀或不可重復讀;ISOLATION_READ_COMMITTED:允許讀取事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生;ISOLATION_REPEATABLE_READ:對同一字段的多次讀取結果都是一致的,除非數據事務本身改變,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生;ISOLATION_SERIALIZABLE:最高的隔離級別,完全服從ACID的隔離級別,確保不發生臟讀、不可重復讀以及幻讀,也是最慢的事務隔離級別,因為它通常是通過完全鎖定事務相關的數據庫表來實現的。
傳播行為
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底層采用保存點機制,形成嵌套事務。
總結起來就是這張圖:
是否只讀
如果將事務設置為只讀,表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務。
注意事務的是否“只讀”屬性,不同的數據庫廠商支持不同,需要結合數據庫廠商的具體說明,如Oracle的"readOnly"不起作用,不影響其增刪改查;MySQL的"readOnly"為true時,只能查,增刪改都會拋出異常。
事務超時
事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那么就會自動回滾,而不是一直等待其結束。在TransactionDefinition中以int的值來表示超時時間,默認值是-1(單位是秒)。
設計事務時應當注意,為使應用程序很好地運行,事務不能運行太長時間,因為事務可能涉及對后端數據庫的鎖定,因此長時間的事務會不必要的占用數據庫資源。
回滾規則
回滾規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾。
如果你需要自定義回滾,可以按照如下的策略進行:1、聲明事務在遇到特定的檢查型異常時,像遇到運行期異常那樣回滾;2、聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。
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手動管理事務,在實際應用中很少使用,但是可以了解一下。
編程式事務管理實現方式通常有兩種:事務管理器方式和模板事務方式。
事務管理器(Platform Transaction Manaager)方式,類似應用JTA UserTransaction API方式,但異常處理更簡潔;其核心類為:Spring事務管理的三個接口類以及Jdbc Template類。
模板事務(Transaction Template)方式,此為Spring官方團隊推薦的編程式事務管理方式;主要工具為Jdbc Template類。
聲明式事務管理
聲明式事務管理在實際開發中非常常見,因此需要仔細學習。聲明式事務管理基于AOP模式,對方法進行前后攔截。
聲明式事務管理的配置類型有5種,但是由于3中在Spring2.0后不推薦使用,因此這里主要就是兩種:tx攔截器和全注釋。
聲明式事務管理有三種實現方式:基于TransactionProxyFactoryBean的方式、基于AspectJ的XML方式、基于注解的方式。接下來將以用戶轉賬為例來學習這三種不同的實現方式。
第一步,新建數據庫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開發需要引入的兩個包-->
<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驅動,注意版本號-->
<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數據源-->
<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>
第三步,創建一個實體包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;
}
}
第四步,創建一個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接口的實現類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);
}
}
第五步,創建一個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接口的實現類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;
}
//轉賬操作,source支出方賬戶,destination收款方賬戶,amount轉賬金額
public void transferMoney(String source, String destination, Long amount) {
//付款操作
transferDao.payMoney(source,amount);
int i = 100/0;//此處用于測試拋異常時是否會回滾
//收款操作
transferDao.collectMoney(destination,amount);
}
}
第六步,創建一個db.properties配置文件:
#加載驅動
druid.driverClassName=com.mysql.jdbc.Driver
#加載數據庫
druid.url=jdbc:mysql://localhost:3306/spring_money?useUnicode=true&characterEncoding=UTF-8
#用戶名
druid.username=root
#密碼
druid.password=root
第七步,創建一個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配置文件-->
<!--配置數據源-->
<!--<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"/>
<!--配置數據源-->
<bean id = "dataSource" class = "com.alibaba.druid.pool.DruidDataSource" destroy-method = "close">
<!--數據庫基本信息配置-->
<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文件中添加事務管理器相關配置和TransactionProxyFactoryBean代理對象(前七步的代碼這里不再重復貼出,這里這貼出與本方式相關的代碼):
<!--基于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 :發生哪些異?;貪L事務
* +Exception :發生哪些異常不回滾事務
-->
<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);
}
}
運行結果為:
java.lang.ArithmeticException: / by zero
可以看到執行service事務方法時拋出異常,事務回滾,數據庫中數據未發生改變:(如果將transferMoney方法中的int i = 100/0;代碼注釋掉,則轉賬會成功。)
基于AspectJ的XML方式
首先在applicationContext.xml文件中添加事務管理器的配置、事務的增強以及切面:
<!--基于AspectJ的XML方式-->
<!--配置事務管理器-->
<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.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);
}
}
運行結果為:
java.lang.ArithmeticException: / by zero
可以看到執行service事務方法時拋出異常,事務回滾,數據庫中數據未發生改變:
如果將transferMoney方法中的int i = 100/0;代碼注釋掉,則轉賬會成功:
基于注解的方式
首先在applicationContext.xml文件中添加事務管理器的配置和開啟事務注解:
<!--基于注解的方式-->
<!--配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--開啟事務注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
接著在TransferServiceImpl類中的事務方法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);
}
}
運行結果為:
java.lang.ArithmeticException: / by zero
可以看到執行service事務方法時拋出異常,事務回滾,數據庫中數據未發生改變:
如果將transferMoney方法中的int i = 100/0;代碼注釋掉,則轉賬會成功:
編程式事務管理和聲明式事務管理區別
編程式事務管理允許用戶在代碼中精確定義事務的邊界;聲明式事務管理有助于用戶將操作與事務規則進行解耦(基于AOP交由Spring容器來實現,實現關注點聚焦在業務邏輯上)。
概括來說,編程式事務管理侵入到了業務代碼里面,但是提供了更加詳細的事務管理,而聲明式編程由于基于AOP,所以既能起到事務管理的作用,又可以不影響業務代碼的具體實現。
那么如何選擇編程式事務管理和聲明式事務管理呢?小型應用,事務操作少。建議使用編程式事務管理來實現Transaction Template,因為它簡單、顯示操作、直觀明顯、可以設置事務的名稱。
對于那些大型應用,事務操作多。建議使用聲明式事務管理來實現,因為它業務復雜度高、關聯性緊密,關注點聚焦到業務層面,實現了業務和事務的解耦。
關于事務管理器的選擇,需要基于不同的數據源來選擇相對應的事務管理器,并選擇正確的Platform TransactionManager實現類,而全局事務的選擇可以使用JtaTransactionManager。
聲明式事務管理的三種實現方式總結
在聲明式事務管理的三種實現方式中,基于TransactionProxyFactoryBean的方式需要為每個進行事務管理的類配置一個TransactionProxyFactoryBean對象進行增強,所以開發中很少使用;基于AspectJ的XML方式一旦在XML文件中配置后,不需要修改源代碼,所以開發中經常使用;基于注解的方式開發較為簡單,配置后只需要在事務類上或方法上添加@Transactiona注解即可,所以開發中也經常使用。