原文地址的文章,写的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特性运行。对于嵌套事务,大家可以模拟两个数据源,一方的失败不会影响另一方。