原文地址:http://blog.csdn.net/yoara/article/details/16114853
原文地址的文章,寫的demo會誤導讀者,所以在原文地址文章的基礎上對原作者的demo進行修改,讓demo淺顯易懂。
Demo說明
採用Junit4.10.0+Spring4.2.0+Spring JDBCTemplate+mysql5.1.51(數據庫表的存儲引擎使用 InnoDB,MyISAM存儲引擎是不支持事務的),通過註解方式配置事務,代碼層次包括主測試類,兩個Service對象,事務在Service開啓。
事務概念
注意:從原文地址中,COPY 過來的。
本地事務
數據庫事務,默認事務爲自動提交,因此如果一個業務邏輯類中有多次數據庫操作將無法保證事務的一致性。
Spring事務
對本地事務操作的一次封裝,相當於把使用JDBC代碼開啓、提交、回滾事務進行了封裝。
上述兩個概念會在demo中用到,以方便大家理解代碼。
傳播特性
該特性是保證事務是否開啓,業務邏輯是否使用同一個事務的保證。當事務在傳播過程中會受其影響。其傳播特性包括:
1、Propagation.REQUIRED
方法被調用時自動開啓事務,在事務範圍內使用則使用同一個事務,否則開啓新事務。
2、Propagation.REQUIRES_NEW
無論何時自身都會開啓事務
3、Propagation.SUPPORTS
自身不會開啓事務,在事務範圍內則使用相同事務,否則不使用事務
4、Propagation.NOT_SUPPORTED
自身不會開啓事務,在事務範圍內使用掛起事務,運行完畢恢復事務
5、Propagation.MANDATORY
自身不開啓事務,必須在事務環境使用否則報錯
6、Propagation.NEVER
自身不會開啓事務,在事務範圍使用拋出異常
7、Propagation.NESTED
如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。需要JDBC3.0以上支持。
代碼實例
代碼說明,兩個 實例類,InsertPassword 、 InsertUser, InsertPassword類中的insertPassWord方法 調用了 InsertUser類中的 insertUser 方法。
測試代碼
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.sky.lp.AAtestTransaction.InsertPassword;
public class TestJunit {
@Test
public void testTransaction() {
ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-servlet.xml");
InsertPassword t = (InsertPassword)appCtx.getBean("insertPassword");
t.insertPassWord();
}
}
InsertPassword類代碼
package com.sky.lp.AAtestTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class InsertPassword {
@Autowired
private JdbcTemplate jdbc;
@Autowired
private InsertUser insertUser;
@Transactional(propagation = Propagation.REQUIRED)
public void insertPassWord() {
String[] sql = new String[2];
sql[0] = "INSERT INTO PASSWORD (USERID, PASSWORD, CREATE_TIME) VALUES ('1','qwe', now())";
jdbc.execute(sql[0]);
insertUser.insertUser();
throw new RuntimeException("拋錯");
}
}
InsertUser類代碼
package com.sky.lp.AAtestTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class InsertUser {
@Autowired
private JdbcTemplate jdbc;
@Transactional(propagation = Propagation.REQUIRED)
public void insertUser() {
String[] sql = new String[2];
sql[0] = "INSERT INTO user (name, age, CREATE_TIME) VALUES ('123456789','25', now())";
jdbc.execute(sql[0]);
}
}
spring-servlet.xml 配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<!-- 啓動包掃描功能,以便註冊帶有@Controller、@Service、@repository、@Component等註解的類成爲spring的bean -->
<context:annotation-config />
<context:component-scan base-package="com.sky.lp.AAtestTransaction" />
<!-- 定義數據源 使用 c3po 包 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="name" value="druidOne" />
<property name="url" value="jdbc:mysql://localhost:3306/littledemo" />
<property name="username" value="root" />
<property name="password" value="laixu785^@#"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="initialSize" value="2" />
<property name="maxActive" value="10" />
<property name="minIdle" value="5" />
<property name="validationQuery" value="SELECT COUNT(*) FROM DUAL" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="5000" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 事務控制 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
傳播特性 Propagation.REQUIRED
在 InsertPassword類中的insertPassWord方法 上 添加@Transactional(propagation = Propagation.REQUIRED)。在 InsertUser類中的insertUser方法 上 添加@Transactional(propagation = Propagation.REQUIRED)。
經測試無論在insertPassWord方法還是insertUser方法,如果有一個方法拋出運行時異常,數據提交失敗。 這說明insertPassWord和insertUser使用的是同一個事務,並且只要方法被調用就開啓事務。
傳播特性 Propagation.REQUIRES_NEW
在 InsertPassword類中的insertPassWord方法 上 添加@Transactional(propagation = Propagation.REQUIRES_NEW)。在 InsertUser類中的insertUser方法 上 添加@Transactional(propagation = Propagation.REQUIRES_NEW)。
經測試無論在insertPassWord方法還是insertUser方法,如果其中一個方法拋出運行時異常,不會影響另一個方法事務的數據提交。 這說明insertPassWord和insertUser使用的是不的同事務。
傳播特性 Propagation.SUPPORTS
在 InsertPassword類中的insertPassWord方法 上 添加@Transactional(propagation = Propagation.SUPPORTS)。在 InsertUser類中的insertUser方法 上 添加@Transactional(propagation = Propagation.SUPPORTS)。
經測試如果在insertPassWord中拋出異常,password數據和user數據都被正確提交。說明insertPassWord和insertUser沒有被spring管理和開啓事務,而是使用了本地事務,由於本地事務默認自動提交因此數據都提交成功,但它們使用的卻不是同一個事務,一旦出現異常將導致數據的不一致。
傳播特性 Propagation.NOT_SUPPORTED
在 InsertPassword類中的insertPassWord方法 上 添加@Transactional(propagation = Propagation.REQUIRED)。在 InsertUser類中的insertUser方法 上 添加@Transactional(propagation = Propagation.NOT_SUPPORTED)。
方法 insertPassWord 註釋掉 拋錯代碼,insertUser 在方法末尾 添加
throw new RuntimeException("拋錯");
經測試如果在insertUser 中拋出異常,passwrod數據提交失敗,user數據提交成功。說明insertPassWord開啓了事務,insertUser沒有開啓事務,而是使用了本地事務。
傳播特性 Propagation.MANDATORY
在 InsertPassword類中的insertPassWord方法 上 添加@Transactional(propagation = Propagation.SUPPORTS)。在 InsertUser類中的insertUser方法 上 添加@Transactional(propagation = Propagation.MANDATORY)。
insertPassWord 方法 和 insertUser 方法 都註釋掉 拋錯 代碼
經測試報錯
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
說明 方法 insertUser 必須在事務內執行。
傳播特性 Propagation.NEVER
在 InsertPassword類中的insertPassWord方法 上 添加@Transactional(propagation = Propagation.REQUIRED)。在 InsertUser類中的insertUser方法 上 添加@Transactional(propagation = Propagation.NEVER)。
insertPassWord 方法 和 insertUser 方法 都註釋掉 拋錯 代碼
經測試報錯
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
說明 方法 insertUser 不能在事務內執行。
傳播特性 Propagation.NESTED
在 InsertPassword類中的insertPassWord方法 上 添加@Transactional(propagation = Propagation.NESTED)。在 InsertUser類中的insertUser方法 上 添加@Transactional(propagation = Propagation.NESTED)。
方法 insertPassWord在方法末尾添加
throw new RuntimeException("拋錯");
經測試代碼報錯,user數據和password數據都沒有提交成功。說明其按照REQUIRED特性運行。對於嵌套事務,大家可以模擬兩個數據源,一方的失敗不會影響另一方。