爲何用Spring來進行事務控制?
若是要手動進行控制事務的話,對於JDBC,service層須要Connection;對於Hibernate,serivice層須要Session。若一個項目要實現JDBC和Hibernate或其餘的互換,咱們要作Service層修改不少東西;並且對於Service層來講,他應該關心的不該該是這些,而是業務邏輯。所以,首先手動控制不能實現組件的替換,其次這些API也不該該出如今service層,可是Spring的IOC很好的解決了這樣的問題。java
一.spring事物簡介:mysql
1.spring 事務分爲編程式事務管理,聲明式事務管理兩大類,其中聲明式事物有細分三種方式,兩種經常使用的方式,一種是基於tx和aop名字空間的xml配置文件,另外一種就是基於@Transactional註解。spring
2. 編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。sql
3. 聲明式事務管理創建在AOP之上的。其本質是對方法先後進行攔截,而後在目標方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。聲明式事務最大的優勢就是不須要經過編程的方式管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或經過基於@Transactional註解的方式),即可以將事務規則應用到業務邏輯中。數據庫
顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就能夠得到徹底的事務支持。和編程式事務相比,聲明式事務惟一不足地方是,後者的最細粒度只能做用到方法級別,沒法作到像編程式事務那樣能夠做用到代碼塊級別。可是即使有這樣的需求,也存在不少變通的方法,好比,能夠將須要進行事務管理的代碼塊獨立爲方法等等。編程
4. Spring的事務管理器:數組
Spring事務策略是經過PlatformTransactionManager接口體現的,該接口是Spring事務策略的核心,是一個與任何事務策略分離的接口,隨着底層不一樣事務策略的切換,應用必須採用不一樣的實現類。結合Spring的IoC容器,能夠向該接口注入相關的平臺特性。spring全部的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口,這些事務管理器的的父接口都是PlatformTransactionManager.Spring的事務管理機制是一種典型的策略模式,PlatformTransactionManager表明事務管理接口(該接口定義了下面所說的三個方法),他並不知道底層如何管理事務,他只要求事務管理的實現類提供開始事務(getTransaction())、提交事務(commit())、回滾事務(rollback()),但具體如何實現則交給具體的實現類完成——不一樣的實現類表明不一樣的事務管理策略。緩存
事務管理器實現性能優化 |
目標服務器 |
org.springframework.jdbc.datasource.DataSourceTransactionManager |
在JDBC DataSource中管理事務 (須注入數據源datasource Bean參數) |
org.springframework.orm.hibernate.HibernateTransactionManager |
管理Hibernate事務 (須注入SessionFactory Bean參數) |
org.springframework.orm.jdo.JdoTransactionManager |
管理JDO事務 |
org.springframework.transaction.jta.JtaTransactionManager |
使用一個JTA管理事務,在一個事務跨越多個資源時必須使用 (無須注入參數) |
org.springframework.orm.ojb.PersistenceBrokerTransactionManager |
管理Apache的OJB事務 |
1)、jdbc事務:每一個Connection都帶有一個事務,只是默認被設置爲自動提交。一個鏈接能夠有多個事務。對於JDBC,只有在同一個鏈接內,纔有討論是否提交的前提。
2)、Hibernate事務:本質上也是使用JDBC來處理事務。可是不是直接操做,而是使用Session來操做事務。Session.getTranction();
####事務應該要在service層(也能夠叫事務層)進行控制。
3)、JTA事務管理器無須注入參數,是由於全局事務的JTA資源由JAVA EE服務器提供,而Spring容器能自行從JAVA EE服務器中獲取該事務資源,因此無須使用依賴注入來配置。
當使用JTA全局事務策略時,實際底層須應用服務器支持,而不一樣的應用服務器所提供的JTA全局事務可能存在細節上的差別,所以實際配置全局事務管理器是可能須要使用JtaTransactionManager的子類,如:OC4JtaTransactionManager(Oracle提供的應用服務器)、WebLogicJtaTransactionManager(Bea提供的WebLogic)、UowJtaTransactionManager(IBM提供的WebSphere)等
全局事務和局部事務。全局事務由應用服務器管理,須要底層服務器JTA支持(如WebLogic、JBoss等)。局部事務和底層採用的持久化技術有關:當採用JDBC持久化技術時,須要使用Connetion對象來操做事務;而採用Hibernate持久化技術時,須要使用Session對象來操做事務。
全局事務能夠跨多個事務性的資源(典型例子是關係數據庫和消息隊列);使用局部事務,應用服務器不須要參與事務管理,所以不能保證跨多個事務性資源的事務的正確性。固然,實際上大部分應用都使用單一事務性的資源。
5. spring事務特性,spring全部的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口,
其中TransactionDefinition接口定義如下特性:
1)事務隔離級別
隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:
· TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,一般這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
· TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務能夠讀取另外一個事務修改但尚未提交的數據。該級別不能防止髒讀,不可重複讀和幻讀,所以不多使用該隔離級別。好比PostgreSQL實際上並無此級別。
· TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另外一個事務已經提交的數據。該級別能夠防止髒讀,這也是大多數狀況下的推薦值。
· TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程當中能夠屢次重複執行某個查詢,而且每次返回的記錄都相同。該級別能夠防止髒讀和不可重複讀。
· TransactionDefinition.ISOLATION_SERIALIZABLE:全部的事務依次逐個執行,這樣事務之間就徹底不可能產生干擾,也就是說,該級別能夠防止髒讀、不可重複讀以及幻讀。可是這將嚴重影響程序的性能。一般狀況下也不會用到該級別。
· TransactionDefinition.PROPAGATION_REQUIRED:業務方法須要在一個容器裏運行。若是方法運行時,已經處在一個事務中,那麼加入到這個事務,不然本身新建一個新的事務。
· TransactionDefinition.PROPAGATION_REQUIRES_NEW:無論是否存在事務,該方法總彙爲本身發起一個新的事務。若是方法已經運行在一個事務中,則原有事務掛起,新的事務被建立。
· TransactionDefinition.PROPAGATION_SUPPORTS:該方法在某個事務範圍內被調用,則方法成爲該事務的一部分。若是方法在該事務範圍外被調用,該方法就在沒有事務的環境下執行。
· TransactionDefinition.PROPAGATION_NOT_SUPPORTED:聲明方法不須要事務。若是方法沒有關聯到一個事務,容器不會爲他開啓事務,若是方法在一個事務中被調用,該事務會被掛起,調用結束後,原先的事務會恢復執行。
· TransactionDefinition.PROPAGATION_NEVER:該方法絕對不能在事務範圍內執行。若是在就拋例外。只有該方法沒有關聯到任何事務,才正常執行。
· TransactionDefinition.PROPAGATION_MANDATORY:該方法只能在一個已經存在的事務中執行,業務方法不能發起本身的事務。若是在沒有事務的環境下被調用,容器拋出例外。
· TransactionDefinition.PROPAGATION_NESTED:若是當前存在事務,則建立一個事務做爲當前事務的嵌套事務來運行;若是當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。它使用了一個單獨的事務,這個事務擁有多個能夠回滾的保存點。內部事務的回滾不會對外部事務形成影響。它只對DataSourceTransactionManager事務管理器起效。
· 事務超時
· 所謂事務超時,就是指一個事務所容許執行的最長時間,若是超過該時間限制但事務尚未完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
· 默認設置爲底層事務系統的超時值,若是底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。
· 事務只讀屬性
· @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),這樣就作成一個只讀事務,能夠提升效率。
· 只讀事務用於客戶代碼只讀但不修改數據的情形,只讀事務用於特定情景下的優化,好比使用Hibernate的時候。
· 默認爲讀寫事務。
· 「只讀事務」並非一個強制選項,它只是一個「暗示」,提示數據庫驅動程序和數據庫系統,這個事務並不包含更改數據的操做,那麼JDBC驅動程序和數據庫就有可能根據這種狀況對該事務進行一些特定的優化,比方說不安排相應的數據庫鎖,以減輕事務對數據庫的壓力,畢竟事務也是要消耗數據庫的資源的。
· 可是你非要在「只讀事務」裏面修改數據,也並不是不能夠,只不過對於數據一致性的保護不像「讀寫事務」那樣保險而已。
· 所以,「只讀事務」僅僅是一個性能優化的推薦配置而已,並不是強制你要這樣作不可
2、Spring編程式事務示例
· 步驟1、編寫spring配置文件
· 下面實例使用DataSourceTransactionManager來管理JDBC事務。
查看Spring的配置信息:(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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
· <bean id="propertyConfig"
class="org.springframework.beans.factory.config.
· PropertyPlaceholderConfigurer">
<property name="locations">
· <list>
· <value>classpath:jdbc.properties</value>
· </list>
· </property> </bean>
· <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
· <property name="driverClass" value="${jdbc.driverClassName}" />
· <property name="jdbcUrl" value="${jdbc.url}" />
· <property name="user" value="${jdbc.username}" />
· <property name="password" value="${jdbc.password}" />
· <property name="autoCommitOnClose" value="true"/>
· <property name="minPoolSize" value="10"/><!--鏈接池中保留的最小鏈接數。-->
· <property name="maxPoolSize" value="100" /><!--鏈接池中保留的最大鏈接數。Default: 15 -->
· <property name="maxIdleTime" value="1800" /><!--最大空閒時間,1800秒內未使用則鏈接被丟棄。若爲0則永不丟棄。Default: 0 -->
· <property name="acquireIncrement" value="5" /><!--當鏈接池中的鏈接耗盡的時候c3p0一次同時獲取的鏈接數。Default: 3 -->
· <property name="maxStatements" value="0" />
· <!-- maxStatementsPerConnection定義了鏈接池內單個鏈接所擁有的最大緩存statements數。Default: 0 -->
· <property name="maxStatementsPerConnection" value="0"/>
· <property name="initialPoolSize" value="10" />
· <property name="idleConnectionTestPeriod" value="3600" /><!--每60秒檢查全部鏈接池中的空閒鏈接。Default: 0 -->
· <property name="acquireRetryAttempts" value="30" /><!--定義在從數據庫獲取新鏈接失敗後重復嘗試的次數。Default: 30 -->
· <property name="breakAfterAcquireFailure" value="false" />
· <property name="testConnectionOnCheckout" value="false" />
· </bean>
· <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
· <!-- JDBC事務管理器 注意:事務管理器傳的參數是數據源-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.
· DataSourceTransactionManager" scope="singleton">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
· <!-- 聲明事務模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
</bean>
· <bean id="bankDao" class="com.sunflower.dao.BankDaoImp">
<property name="jdbcTemplate">
<ref bean="jdbcTemplate" />
</property>
<property name="transactionTemplate">
<ref bean="transactionTemplate" />
</property>
</bean>
· 上 面代碼中配置了一個org.springframework.transaction.support.TransactionTemplate實例,要 在代碼中添加事務,Spring爲咱們提供了一種方法就是使用TransactionTemplate類。咱們要爲 TransactionTemplate裝配一個TransactionManager,
· 若是是要配置Hibernate事務,要進行以下配置:(配置一個sessionFactory):
<!-- Hibernate事務管理器 注意:此事務管理器參數是sessionFactory-->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager" scope="singleton">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
· 若是是要配置JTA事務,要進行以下配置(無須參數):
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager" scope="singleton" >
</bean>
· 步驟2、使用TransactionTemplate進行事務管理:
package com.sunflower.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.sunflower.entity.People;
public class BankDaoImp implements BankDao {
private JdbcTemplate jdbcTemplate;
· //注入聲明式事物模板
private TransactionTemplate transactionTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public TransactionTemplate getTransactionTemplate() {
return transactionTemplate;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public double getMoney(final People people) {
double money = people.getMoney();
// 開始事務,若是出現情況則回滾
transactionTemplate.execute(new TransactionCallback<People>() {
@Override
public People doInTransaction(TransactionStatus ts) {
try {
final People people2 = new People();
// 使用JdbcTemplate進行持久化層操做
String sql = "select money from bank where name = ?";
Object[] params = new Object[] { people.getName() };
// 查詢
jdbcTemplate.query(sql, params, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs)
throws SQLException {
people2.setMoney(rs.getDouble("money"));
System.out.println(people.getName() + "用戶還有"
+ rs.getDouble("money") + "元餘款");
System.out.println(people.getName() + "要從帳戶中取出"
+ people.getMoney() + "元");
if (people2.getMoney() < people.getMoney()) {
System.out.println("餘額不足");
people.setMoney(-1);
return;
}
}
});
if (people.getMoney() < 0)
return null;
else {
sql = "update bank set money = ? where name = ?";
Object[] params2 = new Object[] {
people2.getMoney() - people.getMoney(),
people.getName() };
jdbcTemplate.update(sql, params2);
System.out.println("剩餘餘額:"
+ (people2.getMoney() - people.getMoney()));
}
}
catch (Exception e) {
ts.setRollbackOnly();
}
// 若是成功,事務被提交
return people;
}
});
return people.getMoney();
}
}
調 用TransactionTemplate實例的execute()方法將執行包含在TransactionCallback實例裏的代碼。若是代碼出現 異常,調用TransactionStatus對象的setRollbackOnly()將事務回滾。不然,若是doInTransaction()方法 正常返回,事務將被提交。
一、基於TransactionProxyFactoryBean的方式(不多使用)
須要爲每一個事務管理的類配置一個TransactionProxyFactoryBean進行管理。使用時還須要在類中注入該代理類。
二、基於AspectJ的方式(常使用)
配置好以後,按照方法的名字進行管理,無需再類中添加任何東西。
能夠配置多個切入點,以及異常處理切面
在spring的事務管理中,咱們可使用@Transactional這一annotation來對事務進行聲明式的設定。具體而言,就是在類或者方法前添加@Transactional並傳入屬性參數以獲取所須要的Transaction特性。Spring中的@Transactional有5個屬性:Propagation、Isolation、Rollback Rules、Timeout和Read-Only,其中Propagation屬性定義了Transaction的邊界 — 是否使用Transaction、在Transaction已存在的狀況下如何表現等。
在service類前加上@Transactional,聲明這個service全部方法須要事務管理。每個業務方法開始時都會打開一個事務,在單獨使用不帶任何參數的 @Transactional 註釋時,傳播模式要設置爲 REQUIRED,只讀標誌設置爲 false,事務隔離級別設置爲 READ_COMMITTED,並且事務不會針對受控異常(checked exception)回滾。
@Transactional屬性
屬性 |
類型 |
描述 |
value |
String |
可選的限定描述符,指定使用的事務管理器 |
propagation |
enum: Propagation |
可選的事務傳播行爲設置 |
isolation |
enum: Isolation |
可選的事務隔離級別設置 |
readOnly |
boolean |
讀寫或只讀事務,默認讀寫 |
timeout |
int (in seconds granularity) |
事務超時時間設置 |
rollbackFor |
Class對象數組,必須繼承自Throwable |
致使事務回滾的異常類數組 |
rollbackForClassName |
類名數組,必須繼承自Throwable |
致使事務回滾的異常類名字數組 |
noRollbackFor |
Class對象數組,必須繼承自Throwable |
不會致使事務回滾的異常類數組 |
noRollbackForClassName |
類名數組,必須繼承自Throwable |
不會致使事務回滾的異常類名字數組 |
用法
@Transactional 能夠做用於接口、接口方法、類以及類方法上。看成用於類上時,該類的全部 public 方法將都具備該類型的事務屬性,同時,咱們也能夠在方法級別使用該標註來覆蓋類級別的定義。
雖然 @Transactional 註解能夠做用於接口、接口方法、類以及類方法上,可是 Spring 建議不要在接口或者接口方法上使用該註解,由於這隻有在使用基於接口的代理時它纔會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。若是你在 protected、private 或者默承認見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常。
默認狀況下,只有來自外部的方法調用纔會被AOP代理捕獲,也就是,類內部方法調用本類內部的其餘方法並不會引發事務行爲,即便被調用方法使用@Transactional註解進行修飾。
Spring默認狀況下會對運行期例外(RunTimeException)進行事務回滾。這個例外是unchecked
若是遇到checked意外就不回滾。
如何改變默認規則:
1 讓checked例外也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class)
2 讓unchecked例外不回滾: @Transactional(notRollbackFor= RuntimeException.class)
<!-- 配置Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"/>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" />
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
transaction-manager 屬性保存一個對在 Spring 配置文件中定義的事務管理器 bean 的引用。這段代碼告訴 Spring 在應用事務攔截器時使用 @Transaction 註釋。若是沒有它,就會忽略 @Transactional 註釋,致使代碼不會使用任何事務。
或者
至此spring事務相關的總結結束,本人週末閒來無事,花點時間總結下事務相關的內容,網上好多事務的說明,可是感受總結的不全面,借鑑度娘上資源及本身的一點經驗,但願對新人有點幫助,不過期間有點緊,不免會有總結不周全或不許確的地方,若是有更好的建議,但願碼農們提出寶貴意見,後續完善。
2個問題,討論下,相信你們本身手動試驗後會有正確的結果(一週後答案解釋附上):
question1:
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
public long insertTrade(TradeData trade) throws Exception {
insertTrade();//這是一條插入sql語句,1.數據會不會插入數據庫?2.直接拋出異常 ?
System.out.println(4/0);
}
question2:
@Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public long insertTrade(TradeData trade) throws Exception {
insertTrade()//這是一條插入sql語句,1.數據會不會插入數據庫?2.直接拋出異常 ?
System.out.println(4/0);
}