Spring中的事务管理(四)
全有或全无的操作称为事务。事务允许你将几个操作组合成一个要么发生要么不发生的工作单元。我们可以用四个词来表示事务:
原子性: 原子性确保事务中的所有操作全部发生或全部不发生。(所有操作成功,事务也就成功;任意一个操作失败,事务就失败并回滚)。
一致性: 一旦事务完成,系统必须确保它所建模的业务处于一直状态。
隔离性: 事务允许多个用户对相同的数据进行操作,所以所有的操作应该相隔离。
持久性: 一旦事务完成,事务的结果应该持久化。
在介绍Spring的事务管理之前,我们首先要介绍一下Spring的JDBC模板技术。
Spring框架的JDBC模板技术
Spring框架中提供了很多模板类来实现简化编程。比如最基本的JDBC模板(Spring框架提供了JdbcTemplate
类)
我们以以一个简单的案例来演示如何使用Spring框架提供的JDBC模板类
-
创建数据表结构
1 2 3 4 5 6 7
create database springlearn; use springlearn; create table t_account( id int primary key auto_increment, name varchar(100), money double );
-
方式一:通过new
DriverManagerDataSource
来插入数据1 2 3 4 5 6 7 8 9 10 11 12 13
@Test public void run(){ //创建连接池,使用Spring框架内置的连接池 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///springlearn"); dataSource.setUsername("root"); dataSource.setPassword("root"); //创建模板类 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); //完成数据的添加 jdbcTemplate.update("insert into t_account values (null,?,?)","TyCoding",1000); }
此时已经在表中添加了一行数据。
注意:
-
jdbc:mysql:///
等价于jdbc:mysql://localhost:3306
- 此时我们先不要插入中文,可能会出现乱码情况。
使用Spring框架来管理模板类
上面的案例中我们使用的是new的方式来创建的jdbc模板类,下面我们用Spring来管理这些模板类
-
spring.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
<?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" 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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <context:component-scan base-package="spring_2"/> <!-- Spring的数据库连接池 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///springlearn"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- Spring的模板类 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 将连接池对象注入到模板类中 --> <property name="dataSource" ref="dataSource"/> </bean> </beans>
注意:
类似上面我们使用new的方式创建对象,步骤仍是先创建连接池对象(此处使用的Spring内置的连接池对象),然后创建模板类对象(并将连接池对象注入到模板类对象中)
-
Test测试类
1 2 3 4 5 6 7 8 9 10 11
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring.xml") public class Demo4Test { @Resource(name="jdbcTemplate") private JdbcTemplate jdbcTemplate; @Test public void run2(){ jdbcTemplate.update("insert into t_account values(null,?,?)","TyCoding2",1000); } }
注意:
- Spring整合了JUnit测试,所以我们这里使用了Spring的注解方法加载配置文件
spring.xml
-
@RunWith()
用于获取测试类对象 -
@ContextConfiguration()
用于加载配置文件
-
- 大家还记得前面我们已经介绍了Spring的注入对象方式,其中
@Resource
和@Autowired
注解都可以实现将一个Java对象交给Spring,通过加载Spring上下文来注入此对象。因为此时我们在Java类中注入的Bean对象名是jdbcTemplate
,而spring.xml
中也配置了名字是jdbcTemplate
Bean的属性,那么在Java类中的jdbcTemplate
对象就拥有了一些属性。增加name属性仅是为了缩小查找Bean的范围。 - 综上我们注入了Bean对象,并加载了配置文件,就可以直接执行相关sql语句了
Spring框架的事务管理
上面我们简单的介绍了Spring框架如何通过模板类执行SQL语句的,下面我们就分析一下Spring对事务的管理:
上面我们已经介绍了事务的四个特性。
原子性
一致性
隔离性
持久性
下面我们仍要了解一些名词概念
- Spring的事务分类:
编码式事务
声明式事务
-
事务的属性
传播行为: 传播行为定义了客户端与被调方法之间的事务边界。(Spring定义了7中不同的传播行为)
隔离级别:定义一个事务可能受到其他并发事务的影响程度。- 脏读:一个事务读取了另一个事务尚改写但尚未提交的数据
- 不可重复读:一个事务执行相同的查询多次,每次得到不同数据。(并发访问造成的)
- 幻读:类似不可重复读。一个事务读取时遇到另一个事务的插入,则这个事务就会读取到一些原本不存在的记录。
只读:事务只读时,数据库就可以对其进行一些特定的优化。
事务超时:事务运行时间过长。
回滚原则:定义那些异常会导致事务回滚。(默认情况只有运行时异常才事务回滚)
-
Spring框架的事务管理相关的类和API
1.PlatformTransactionManager接口:平台事务管理器(真正管理事务的类)
该接口的实现类: 1.`DataSourceTransactionManager`:用在当使用Spring的JDBC模板类或Mybatis框架时 2.`HibernateTransactionManager`:使用Hibernate框架时
2.TransactionDefinition接口:事务定义信息(事务隔离级别、传播行为、超时、只读)
3.TransactionStatus接口:事务的状态
声明式事务
Spring有两种事务,但是我们这里只介绍声明式事务,编码式事务不常用,所以这里不再介绍。
在案例之前我们了解一下2种常用连接池的不同写法:
2种连接池
-
c3p0
1 2 3 4 5 6 7
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--配置链接属性--> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.username}"/> </bean>
-
druid 阿里的连接池
1 2 3 4 5 6 7
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <!--配置链接属性--> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
注意:采用${jdbc.xx}
的写法前提是在外边定义了xx.properties
文件,并在spring.xml
中引入了该配置文件
在XML中定义事务
下面开始我们的案例
- 首先我们要引入Spring提供的
tx
命名空间1 2 3 4 5 6 7 8 9 10 11 12 13 14
<?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-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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
注意
不但需要tx
的命名空间,还需要aop
的命名空间,以为Spring的声明式事务式的支持是通过Spring AOP框架实现的。
通过一个XML片段了解一下<tx:advice>
是如何声明事务性策略的:
1 2 3 4 5 6 |
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> |
解释:<tx:advice>
实现声明事务性策略,所有的事务配置都在改元素下定义。<tx:method>
元素为name属性指定的方法定义事务参数。我们看一下<tx:method>
的属性:
隔离级别|含义
-|:-:|-:
isolation|指定事务的隔离级别
propagation|定义事务的传播规则
read-only|指定事务为只读
回滚规则:
rollback-for
no-rollback-for|rollback-for指定了事务对于那些检查型异常应当回滚而不提交
no-rollback-for指定事务对于那些异常应当继续执行而不回滚
timeout|对于长时间运行的事务定义超时时间
当使用<tx:advice>
来声明事务时,我们还需要一个事务管理器(常用DataSourceTransactionManager
),然后使用transaction-manager
属性指定事务管理器的id:
1 2 3 |
<tx:advice id="txAdvice" transaction-manager="txManager"> ... </tx:advice> |
<tx:advice>
仅定义了AOP通知,用于把事务边界通知给方法。但这些只是事务通知,而不是完成的事务性切面。所以我们还需要使用<aop:config>
定义一个通知器(advisor)
1 2 3 |
<aop:config> <aop:advisor pointcut="execution(* xx.xx.xxx(..))" advice-ref="txAdvice"> </aop:config> |
看了上面的解释你是否有一些思路了呢?下面我们通过案例(转账)来体会一下:
- 目录结构:
初始数据:
-
AccountService.java
1 2 3 4
package spring_2; public interface AccountService { void pay(String out, String in, double money); }
-
AccountServiceImp.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
package spring_2; import javax.annotation.Resource; public class AccountServiceImp implements AccountService { @Autowired private AccountDao accountDao; public void pay(String out, String in, double money) { //扣钱 accountDao.outMoney(out,money); //模拟异常 //int a = 10/0; //加钱 accountDao.inMoney(in,money); } }
-
AccountDao.java
1 2 3 4 5
package spring_2; public interface AccountDao { void outMoney(String out,double money); void inMoney(String in,double money); }
-
AccoundDaoImp.java
1 2 3 4 5 6 7 8 9 10
package spring_2; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class AccountDaoImp extends JdbcDaoSupport implements AccountDao { public void outMoney(String out,double money){ this.getJdbcTemplate().update("update t_account set money = money - ? where name = ?",money,out); } public void inMoney(String in,double money){ this.getJdbcTemplate().update("update t_account set money = money + ? where name = ?",money,in); } }
-
spring.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
<?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-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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!-- 使用c3p0的连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///springlearn"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 注入Bean --> <bean id="accountService" class="spring_2.AccountServiceImp"/> <bean id="accountDao" class="spring_2.AccountDaoImp"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事务增强 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- name :绑定事务的方法名,可以使用通配符,可以配置多个 propagation :传播行为 isolation :隔离级别 read-only :是否只读 timeout :超时信息 rollback-for :发生哪些异常回滚 no-rollback-for :发生哪些异常不回滚 --> <tx:method name="pay" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置AOP切面代理 --> <aop:config> <!-- 如果是自己写的切面,使用<aop:aspect>标签;如果是系统的,用<aop:advisor> --> <aop:advisor advice-ref="txAdvice" pointcut="execution(* spring_2.AccountServiceImp.pay(..))"/> </aop:config> </beans>
-
Test测试类
1 2 3 4 5 6 7 8 9 10 11
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring.xml") public class AccountTest { @Autowired private AccountService accountService; @Test public void run3(){ accountService.pay("TyCoding","tutu",100); } }
定义注解驱动的事务
我们不但可以使用XML定义事务驱动,还可以用使用注解@Transaction
秩序要改动spring.xml
和AccountServiceImp.java
即可以大大简化代码,如下:
-
AccountServiceImp.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@Transactional public class AccountServiceImp implements AccountService { @Autowired private AccountDao accountDao; public void pay(String out, String in, double money) { //扣钱 accountDao.outMoney(out,money); //int a = 10/0; //加钱 accountDao.inMoney(in,money); } }
-
spring.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
<?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-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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <context:component-scan base-package="spring_2"/> <!-- 使用c3p0的连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///springlearn"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 注入Bean --> <bean id="accountService" class="spring_2.AccountServiceImp"/> <bean id="accountDao" class="spring_2.AccountDaoImp"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> <!--<!– 配置事务增强 –> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!– name :绑定事务的方法名,可以使用通配符,可以配置多个 propagation :传播行为 isolation :隔离级别 read-only :是否只读 timeout :超时信息 rollback-for :发生哪些异常回滚 no-rollback-for :发生哪些异常不回滚 –> <tx:method name="pay" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!– 配置AOP切面代理 –> <aop:config> <!– 如果是自己写的切面,使用<aop:aspect>标签;如果是系统的,用<aop:advisor> –> <aop:advisor advice-ref="txAdvice" pointcut="execution(* spring_2.AccountServiceImp.pay(..))"/> </aop:config>--> </beans>
如图所示,我们只需要写一个<tx:annotation-driven>
即可代替如上的XML配置,它允许在最有意义的位置声明事务规则:在事务性方法上。
<tx:annotation-driven>
元素告诉Spring检查上下文中所有的Bean并查找使用@Transaction
注解的Bean,而不管这个注解是用在类级方法上还是方法级别上。
对于每一个使用@Transactional
注解的Bean,<tx:annotation-driven>
会自动为它添加事务通知。通知的事务属性是通过@Transactional
注解的参数定义的。
如上无论哪种方式,当我们把AccountServiceImp.java
中的int a = 10 / 0;
以上都会报错,事务并回滚。