Spring(九)----Spring的事务管理

一、事务的回顾

1. 事务:指的是逻辑上一组操作,组成这个事务的各个执行单元,要么一起成功,要么一起失败!
2. 事务的特性
* 原子性
* 一致性
* 隔离性
* 持久性
3. 如果不考虑隔离性,引发安全性问题
* 读问题:
* 脏读:
* 不可重复读:
* 虚读:
* 写问题:
* 丢失更新:
4. 如何解决安全性问题
* 读问题解决,设置数据库隔离级别
* 写问题解决可以使用 悲观锁和乐观锁的方式解决

二、Spring框架的事务管理相关的类和API

要通过spring来完成事务的管理,我们需要使用spring提供的一些类和一些方法。

1. PlatformTransactionManager接口

平台事务管理器(真正管理事务的类)。该接口有具体的实现类,根据不同的持久层框架,需要选择不同的实现类!

spring就用这个接口来管理事务。

我们掌握两个实现类就好了。

DataSourceTransactionManager和HibernateTransactionManager

如果,我们使用的是jdbcTemplate就要使用DataSourceTransactionManager;如果,使用了hibernate,就要使用HibernateTransactionManager。

2. TransactionDefinition接口

事务定义信息(事务的隔离级别,传播行为,超时,只读)。

3. TransactionStatus接口

事务的状态

4. 总结

上述对象之间的关系:平台事务管理器真正管理事务对象。根据事务定义的信息TransactionDefinition 进行事务管理,在管理事务中产生一些状态。将状态记录到TransactionStatus中
5. PlatformTransactionManager接口中实现类和常用的方法
5.1. 接口的实现类
* 如果使用的Spring的JDBC模板或者MyBatis框架,需要选择DataSourceTransactionManager实现类
* 如果使用的是Hibernate的框架,需要选择HibernateTransactionManager实现类
5.2. 该接口的常用方法
* void commit(TransactionStatus status) 
* TransactionStatus getTransaction(TransactionDefinition definition) 
* void rollback(TransactionStatus status) 

6. TransactionDefinition

6.1. 事务隔离级别的常量
* static int ISOLATION_DEFAULT -- 采用数据库的默认隔离级别
* static int ISOLATION_READ_UNCOMMITTED 
* static int ISOLATION_READ_COMMITTED 
* static int ISOLATION_REPEATABLE_READ 
* static int ISOLATION_SERIALIZABLE 
6.2. 事务的传播行为常量(不用设置,使用默认值)
* 先解释什么是事务的传播行为:解决的是业务层之间的方法调用!!
* PROPAGATION_REQUIRED(默认值) -- A中有事务,使用A中的事务。如果没有,B就会开启一个新的事务,将A包含进来。(保证A,B在同一个事务中),默认值!!
* PROPAGATION_SUPPORTS -- A中有事务,使用A中的事务。如果A中没有事务。那么B也不使用事务。
* PROPAGATION_MANDATORY -- A中有事务,使用A中的事务。如果A没有事务。抛出异常。
* PROPAGATION_REQUIRES_NEW(记)-- A中有事务,将A中的事务挂起。B创建一个新的事务(保证A,B没有在一个事务中)。
* PROPAGATION_NOT_SUPPORTED -- A中有事务,将A中的事务挂起。
* PROPAGATION_NEVER -- A中有事务,抛出异常。
* PROPAGATION_NESTED(记) -- 嵌套事务。当A执行之后,就会在这个位置设置一个保存点。如果B没有问题。执行通过。如果B出现异常,运行客户根据需求回滚(选择回滚到保存点或者是最初始状态

三、搭建事务管理转账案例的环境

强调:简化开发,以后DAO可以继承JdbcDaoSupport类

1. 步骤一:创建WEB工程,引入需要的jar包
* IOC的6个包
* AOP的4个包
* C3P0的1个包
* MySQL的驱动包
* JDBC目标2个包
* 整合JUnit测试包

2. 步骤二:引入配置文件
* 引入log4j.properties
* 引入applicationContext.xml

<!-- 配置C3P0的连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver" />
	<property name="jdbcUrl" value="jdbc:mysql:///spring_day03" />
	<property name="user" value="root" />
	<property name="password" value="123456" />
</bean>

<!-- 配置jdbc的模板类 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" ref="dataSource" />
</bean>
3. 步骤三:创建对应的包结构和类
4. 步骤四:引入Spring的配置文件,将类配置到Spring中
<!-- 配置业务层和持久层 -->
<bean id="accountDao" class="com.ken.demo1.AccountDaoImpl" />

<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
	<property name="accountDao" ref="accountDao" />
</bean>
5. 步骤五:在业务层注入DAO ,在DAO中注入JDBC模板(强调:简化开发,以后DAO可以继承JdbcDaoSupport类)

dao层实现类:

public class AccountDaoImpl implements AccountDao {

	private JdbcTemplate jdbcTemplate;

	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	/**
	 * 扣钱
	 * 
	 * @param out
	 * @param money
	 */
	@Override
	public void outMoney(String out, double money) {
		jdbcTemplate.update("update t_account set money = money - ? where name = ? ", money, out)
	}

	/**
	 * 加钱
	 * 
	 * @param in
	 * @param money
	 */
	@Override
	public void inMoney(String in, double money) {
		jdbcTemplate.update("update t_account set money = money + ? where name = ? ", money, in)
	}

}
配置:
<!-- 配置业务层和持久层 -->
<bean id="accountDao" class="com.ken.demo1.AccountDaoImpl">
	<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
	<property name="accountDao" ref="accountDao" />
</bean>
但是,如果,我们在每个dao的实现类里面都要一一去配置jdbcTemplate,就很麻烦了。

我们可以给每个dao的实现类继承一个父类,然后,父类中有注入jdbcTemplate的这段代码,也就是下图的代码


继承父类之后,如下:

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
	/**
	 * 扣钱
	 * 
	 * @param out
	 * @param money
	 */
	@Override
	public void outMoney(String out, double money) {
		this.getJdbcTemplate().update(" update t_account set money = money - ? where name = ? ", money, out);
	}

	/**
	 * 加钱
	 * 
	 * @param in
	 * @param money
	 */
	@Override
	public void inMoney(String in, double money) {
		this.getJdbcTemplate().update(" update t_account set money = money + ? where name = ? ", money, in);
	}
}
我们看一下父类的源码:



并且,这个方法是final的。说明子类不能重写这个方法。

那么,子类如何使用父类提供的jdbcTemplate呢?父类提供了get方法。


我们在分析一段父类的源码:


这个意思就是说,如果,这个dao没有配置jdbcTemplate但配置了dataSource,那么这个类会用我们配置的dataSrouce去帮我们创建一个jdbcTemplate。这样一来,我们可以修改一下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">

	<!-- 配置C3P0的连接池 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql:///spring_day03" />
		<property name="user" value="root" />
		<property name="password" value="123456" />
	</bean>

	<!-- 配置jdbc的模板类 -->
	<!-- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean> -->

	<!-- 配置业务层和持久层 -->
	<bean id="accountDao" class="com.ken.demo1.AccountDaoImpl">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
		<property name="accountDao" ref="accountDao" />
	</bean>
</beans>

源码下载

我们在service的实现类中添加一个异常:


再运行测试类,结果是,outMoney()方法执行了,inMoney()方法没执行。这就引出一个问题,我们要通过事务来控制业务层的操作。

四、spring的事务管理的分类

spring有两种方式来管理事务:编程式事务管理、声明式事务管理

不管是哪种方式,都用平台事务管理器(PlatformTransactionManager)

4.1 spring编程式事务管理(了解)

正常jdbc连接数据库,就是通过connection,只有connection才能操作事务。这是最底层。


步骤一:配置一个事务管理器,Spring使用PlatformTransactionManager接口来管理事务,所以咱们需要使用到他的实现类!

<!-- 配置平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
Spring为了简化事务管理的代码:提供了模板类 TransactionTemplate,所以手动编程的方式来管理事务,只需要使用该模板类即可。

步骤二:配置事务管理的模板

<!-- 手动编码,提供了模板类,使用该类管理事务比较简单 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
	<property name="transactionManager" ref="transactionManager" />
</bean>

步骤三:在需要进行事务管理的类中,注入事务管理的模板
<bean id="accountService" class="com.ken.demo1.AccountServiceImpl">
	<property name="accountDao" ref="accountDao" />
	<property name="transactionTemplate" ref="transactionTemplate" />
</bean>

步骤四:在业务层使用模板管理事务

// 注入事务模板对象
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
	this.transactionTemplate = transactionTemplate;
}
public void pay(final String out, final String in, final double money) {

	transactionTemplate.execute(new TransactionCallbackWithoutResult() {
		//事务的执行,如果没有问题,提交。如果出现异常,回滚。
		@Override
		protected void doInTransactionWithoutResult(TransactionStatus arg0) {
			// 先扣钱
			accountDao.outMoney(out, money);
			int a = 10 / 0;
			// 再加钱
			accountDao.inMoney(in, money);
		}
	});
}

执行测试方法,有异常,但是,钱不转。

spring_tx编程式事务代码

4.2 声明式事务

Spring框架的事务管理之声明式事务管理,即通过配置文件来完成事务管理(AOP思想)。

声明式事务管理又分成两种方式:基于AspectJ的XML方式(重点掌握)和基于AspectJ的注解方式(重点掌握)

4.2.1 基于AspectJ的XML方式

我们把项目的包和配置文件复制出来一份:




事务管理的底层基于aop。不改业务层的代码,aop帮我们加事务。aop需要写切面类,切面里面需要些通知,然后再配置一下。以前,我们做demo的时候“记录日志”需要自己写。现在,spring的事务管理已经帮我们写好了事务管理代码,切面类不用我们编写了,我们只要配置一下。spring的事务的代码已经写好了,类已经准备好了。我们得把它配好。

步骤一:恢复转账开发环境

步骤二:引入AOP的开发包

步骤三:配置事务管理器

<!-- 配置平台事务管理器 -->
<bean id="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
步骤四:配置事务增强
<!-- 声明式事务(采用XML配置文件的方式) -->
<!-- 先配置通知 -->
<tx:advice id="myAdvice" 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 如果是自己编写的aop,使用aop:aspect配置;如果,使用的是spring框架提供的通知,使用aop:advisor-->
<aop:config>
	<!-- aop:advisor,是spring框架提供的通知 -->
	<aop:advisor advice-ref="myAdvice" pointcut="execution(public * com.ken.demo2.AccountServiceImpl.pay(..))"/>
</aop:config>
步骤六:编写测试类

见源码

4.2.2 基于AspectJ的注解方式

重点掌握,最简单的方式

步骤一:恢复转账的开发环境

步骤二:配置事务管理器

<!-- 配置平台事务管理器 -->
<bean id="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
步骤三:开启注解事务
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
步骤四:在业务层上添加一个注解--@Transactional
在类上添加注解,类中的所有的方法全部都有事务;
也可以只在某个方法上加。

步骤五:编写测试类

见源码

源码


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值