一、事务的回顾
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);
}
});
}
执行测试方法,有异常,但是,钱不转。
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
在类上添加注解,类中的所有的方法全部都有事务;
也可以只在某个方法上加。
步骤五:编写测试类
见源码