事务回顾
1)什么是事务?
事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
2)事务的特性(ACID)
-
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
-
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
-
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
-
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
3)TransactionDefinition:事务定义信息
事务定义信息有:
-
隔离级别
-
传播行为
-
超时信息
-
是否只读
4)如果不考虑隔离性会引发安全性问题
隔离级别:定义了一个事务可能受其他并发事务影响的程度。
并发事务引起的问题:典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致以下的问题。
-
脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
-
不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
-
幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
5)解决读问题:设置事务的隔离级别
-
未提交读:脏读,不可重复读,虚读都有可能发生
-
已提交读:避免脏读。但是不可重复读和虚读都有可能发生
-
可重复读:避免脏读和不可重复读。但是虚读有可能发生
-
串行化的:避免以上所有读问题
mysql数据库的默认隔离级别就是可重复读,Oracle默认是已提交读
6)事务的传播行为
PROPAGATION_XXX:事务的传播行为。
-
保证在同一个事务中
PROPAGATION_REQUIRED:required , 必须。支持当前事务,如果不存在,就新建一个(默认)
PROPAGATION_SUPPORTS:supports ,支持。支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY:mandatory ,强制。支持当前事务,如果不存在,就抛出异常 -
保证没有在同一个事务中
PROPAGATION_REQUIRES_NEW:requires_new,必须新的。如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED:not_supported ,不支持。以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER:never,从不。以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED:nested ,嵌套。如果当前事务存在,则嵌套事务执行
关于事务的传播行为我会另起一篇详细介绍,最好要理解,实在不理解也没关系,随着时间的推移,代码的进步会慢慢理解的。
7)事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
8)只读
这是事务的第三个特性,是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
Spring进行事务管理的常用API
1)PlatformTransactionManager:平台事务管理器
Spring进行事务操作时候,主要使用一个PlatformTransactionManager接口,它表示事务管理器,即真正管理事务的对象。
Spring并不直接管理事务,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,也就是将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring针对不同的持久化框架,提供了不同PlatformTransactionManager接口的实现类:
-
org.springframework.jdbc.datasource.DataSourceTransactionManager :使用 Spring JDBC或iBatis 进行持久化数据时使用
-
org.springframework.orm.hibernate3.HibernateTransactionManager :使用 Hibernate版本进行持久化数据时使用
2)Spring的这组接口是如何进行事务管理的
平台事务管理器根据事务定义的信息进行事务的管理,事务管理的过程中产生一些状态,将这些状态记录到TrancactionStatus里面。
3)TransactionStatus:事务的状态
在上面 PlatformTransactionManager 接口有一个方法getTransaction(),这个方法返回的是 TransactionStatus对象,然后程序根据返回的对象来获取事务状态,然后进行相应的操作。
而 TransactionStatus 这个接口的内容如下:
这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。
Spring中进行事务操作–案例
Spring进行事务操作的方式
- 编程式事务管理
- 声明式事务管理
- 基于xml配置文件方式
- 基于注解方式
搭建案例环境
例子就是模拟银行转账,首先要搭建好转账的环境。
1)搭建数据库环境
## 创建表
CREATE TABLE `NewTable` (
`id` int(4) NOT NULL AUTO_INCREMENT ,
`name` varchar(7) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
`balance` decimal(11,0) NOT NULL DEFAULT 0 ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci;
## 插入数据
INSERT INTO `spring_jdbc`.`account` (`id`, `name`, `balance`) VALUES ('1', 'zs', '1000');
INSERT INTO `spring_jdbc`.`account` (`id`, `name`, `balance`) VALUES ('2', 'ls', '1000');
2)创建一个web项目,导入jar包
3)在Spring配置文件中开启组件扫描,配置数据库连接池环境以及jdbcTemplate
<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/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!--指定注解扫描包路径-->
<context:component-scan base-package="com.oak"/>
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:dbutil.properties"/>
<!-- 配置dbcp连接池参数 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url"
value="${url}" />
<property name="username" value="${user}" />
<property name="password" value="${password}" />
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="${initsize}" />
<!-- 连接池的最大值 -->
<property name="maxActive" value="${maxsize}" />
<!-- 最大空闲值。当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="${maxIdle}" />
<!-- 最小空闲值。当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="${minIdle}" />
</bean>
<!-- 配置 Spring 的 jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
4)创建dao层转账的具体实现(接口以及实现类)
- dao接口AccountDao
public interface AccountDao {
//减少余额
void lessenBalance(int id,double balance);
//增加余额
void addBalance(int id,double balance);
}
- dao实现类AccountDaoImpl
@Repository
public class AccountDaoImpl implements AccountDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void lessenBalance(int id, double balance) {
jdbcTemplate.update("update account set balance-=? where id=?",balance,id);
}
@Override
public void addBalance(int id, double balance) {
jdbcTemplate.update("update account set balance+=? where id=?",balance,id);
}
}
5)编写service层业务实现(接口和实现类)
- AccountService接口
public interface AccountService {
//实现转账的业务方法
void accountBalance(int lessenId,int addId,double balance);
}
- AccountServiceImpl实现类
@Service
public class AccountServiceImpl implements AccountService{
@Autowired
AccountDao accountDao;
@Override
public void accountBalance(int lessenId, int addId, double balance) {
//某个账号减少金额
accountDao.lessenBalance(lessenId, balance);
//走个账号增加金额
accountDao.addBalance(addId, balance);
}
}
6)在测试类中编程accountTest测试方法
@Test
public void testAccount(){
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accService=ctx.getBean("accountServiceImpl",AccountService.class);
//从id为1的用户转账给id为2的用户500元
accService.accountBalance(1, 2, 500);
}
运行测试查看数据库:
7)转账中出现的问题
加入在转账的过程中出现了一些异常,例如服务器宕机,银行断电等,那这是就会出现一个问题,一个账号的钱转了,另一账号却没收到钱,如下,修改accountBalance方法,模拟出现异常:
public void accountBalance(int lessenId, int addId, double balance) {
//某个账号减少金额
accountDao.lessenBalance(lessenId, balance);
//模拟出现异常
int a=5/0;
//走个账号增加金额
accountDao.addBalance(addId, balance);
}
}
测试查看数据库:
查看结果,zs转了500,但是ls没收到,这时应该怎么解决这个问题呢?就可使用事务来解决。
Spring进行事务操作
Spring进行事务操作方式
- 编程式事务管理(了解就行,不要求掌握,这里就不做案例了)
- 声明式事务管理
- 基于xml配置文件方式
- 基于注解方式
Spring的声明式事务管理——XML方式:思想就是AOP
基于xml配置文件的方式来进行声明式事务的操作,不需要进行手动编写代码,通过一段配置完成事务管理。下面我们在以上案例的基础上实现它。
1)配置事务管理器
我们已经讲过Spring针对不同的持久化框架,提供了不同PlatformTransactionManager接口的实现类:
-
org.springframework.jdbc.datasource.DataSourceTransactionManager :使用 Spring JDBC或iBatis 进行持久化数据时使用
-
org.springframework.orm.hibernate3.HibernateTransactionManager :使用 Hibernate版本进行持久化数据时使用
所以要在Spring配置文件中加入以下配置:
<!-- 1.配置事务的管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 指定要对哪个数据库进行事务操作 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
2)配置事务的增强,指定对哪个事务管理器进行增强
需要在xml中引入命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
配置事务的增强
<!-- 2.配置事务的增强,指定对哪个事务管理器进行增强 -->
<tx:advice id="txadvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
表示来配置你要增强的方法的匹配的一个规则,
注意:只须改方法的命名规则,其他都是固定的!
propagation:事务的传播行为。
-->
<tx:method name="account*" propagation="REQUIRED"></tx:method>
<!-- <tx:method name="insert*" propagation="REQUIRED"></tx:method> -->
</tx:attributes>
</tx:advice>
3)配置切入点和切面
<!-- 3.配置切入点和切面(最重要的一步) -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.oak.service.AccountService.*(..))" id="pointcut"/>
<!-- 切面,即表示把哪个增强用在哪个切入点上 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
</aop:config>
4)直接测试上个案例中的testAccount方法
先把数据库中的数据恢复到初始的相同余额状态。
直接运行测试方法,运行时异常,查看数据库中的数据,并没有减少,事务被回滚了
Spring的声明式事务的注解方式
基于注解方式来进行声明式事务的操作会更加简单,在实际开发中我们也会用的比较多,我们基于以上案例继续完成
1)配置事务管理器(同上1)
<!-- 1.配置事务的管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 指定要对哪个数据库进行事务操作 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
2)注释掉事务的其他配置,开启事务注解
<!-- 2.开启事务的注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
完成之后的配置文件问:
<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/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--指定注解扫描包路径-->
<context:component-scan base-package="com.oak"/>
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:dbutil.properties"/>
<!-- 配置dbcp连接池参数 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url"
value="${url}" />
<property name="username" value="${user}" />
<property name="password" value="${password}" />
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="${initsize}" />
<!-- 连接池的最大值 -->
<property name="maxActive" value="${maxsize}" />
<!-- 最大空闲值。当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="${maxIdle}" />
<!-- 最小空闲值。当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="${minIdle}" />
</bean>
<!-- 配置 Spring 的 jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 1.配置事务的管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 指定要对哪个数据库进行事务操作 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.开启事务的注解 -->
<tx:annotation-driven transaction-manager="transactionManager"> </tx:annotation-driven>
</beans>
3)在具体使用事务的方法所在的类上面添加注解:@Transactional
在AccountServiceImpl上加上该注解
@Service//业务层注解
@Transactional//事务控制注解
public class AccountServiceImpl implements AccountService{
@Autowired//依赖注入
AccountDao accountDao;
@Override
public void accountBalance(int lessenId, int addId, double balance) {
//某个账号减少金额
accountDao.lessenBalance(lessenId, balance);
//模拟出现异常
int a=5/0;
//走个账号增加金额
accountDao.addBalance(addId, balance);
}
}
4)直接测试
直接测试上个案例的测试方法,数据库中的数据没有发生变法。