认识事务
- 事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
- 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用
- 事务的四个关键属性(ACID)
- 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
- 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
- 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
- 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
比如说一次转账流程:
- A给B转账100元,正常的流程是A账户减少100元,B账户增加100元。
但是如果当A账户减少100元后,程序突然出错了,导致B账户增加100元这个操作没有执行,这时候就会导致这100元不翼而飞… - 这时候如果使用事务,将整个流程放在一个事务中进行,当整个流程正常结束时事务再一次行提交。如果中间出现了什么错误导致流程中断,这时候事务可以回滚,取消这次流程的所有操作。包括已经完成的。
- 也可以把事务当作一个容器或者平台,流程现在这个平武平台上进行,如果没有问题,则将所有结果同步到持久化存储器(数据库)中,如果有问题,就不同步。
下面结合代码看不使用事务会有什么问题:
账户表有两个账户:
下面完成转账的流程:
- 持久化层:
package com.spring.tx.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class TransferDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//通过名字获取账户余额
public int getBalanceByName(String userName){
String sql = "select balance from account where user_name = '"+userName+"'";
int bal = jdbcTemplate.queryForObject(sql, Integer.class);
return bal;
}
/*
* 转出
* 需要判断,如果账户余额不足,抛出运行时异常
*/
public void outcome(String userName,int money){
int bal = this.getBalanceByName(userName);
//如果余额大于转账金额,才扣钱
if(bal > money){
String sql = "update account set balance = "+(bal - money)+" where user_name = '"+userName+"'";
int updateNum = jdbcTemplate.update(sql);
System.out.println("成功更新["+updateNum+"]条数据。");
}else{
//账户余额不足,抛出运行时异常
throw new RuntimeException("余额不足");
}
}
//转入
public void income(String userName,int money){
int bal = this.getBalanceByName(userName);
//转入直接加
String sql = "update account set balance = "+(bal + money)+" where user_name = '"+userName+"'";
int updateNum = jdbcTemplate.update(sql);
System.out.println("成功更新["+updateNum+"]条数据。");
}
}
- 这里使用jdbcTemplate根据进行持久化操作
- 定义了两个核心方法:
outcome :转出,需要判断,如果账户余额不足,抛出运行时异常
income : 转入,不用判断直接入账
- 业务层:
package com.spring.tx.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.spring.tx.dao.TransferDao;
@Service
public class TransferService {
@Autowired
private TransferDao transferDao;
public void transfer(){
//为了测试需要,先入账
transferDao.income("小明", 100);
//再出账
transferDao.outcome("小花", 100);
}
}
定义了transfer转账方法
- 测试:
@Test
public void test2(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-account.xml");
TransferService transferService = (TransferService) ctx.getBean("transferService");
//执行转账
transferService.transfer();
}
- 为了测试需要,这里先进行入账操作,然后再出账。
- 转账前,小明有100元,小花有50元。现在要小花给小明转账100元。
在小花出账时发现余额不足,抛出异常,出账失败。但是小明先进行了入账操作,这时候,整个流程完成了一半,发现莫名其妙多了100元。
这样显然是不行的,这时候就需要用到事务。
声明式事务
- 配置文件:
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 由于这里使用的是jdbcTemplate,所有配置了一个jdbc的事务管理器,对于其他的一些持久化框架比如hibernate,jpa等spring都提供了专门的事务管理器
- 这个事务管理器transactionManager相当于一个平台,所有持久化操作先在这个平台中进行,当使用commit后才会真正持久化到数据库。
- 使用
<tx:annotation-driven/>
开启了事务注解,可以手动在执行流程的方法上使用,使之在事务管理器这个”平台”上执行。
就上面两段,声明式事务配置完成了,是不是很简单。下面开始使用.
在之前的那个业务层的transfer方法上添加事务,让这个转账的整个流程在事务管理”平台”上执行。
@Transactional
public void transfer(){
//为了测试需要,先入账
transferDao.income("小明", 100);
//再出账
transferDao.outcome("小花", 100);
}
运行之前的测试方法
执行结果都是抛出异常,流程完成一半。但是查看数据库结果就和之前不同了:
并没有多出那100元,说明事务起作用了。
回头看上面那个添加了事务的执行结果,抛出异常的截图中可以看到比之前多出很多东西,里面可以看到很多Cglib、invoke、proxy等字样,这些都是在动态代理中涉及到的东西。
没错,事务就是用了动态代理。
spring在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,就是将执行的目标包装在上面那一大段代码中。这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。
继承自RuntimeException或error的是非检查型异常,而继承自Exception的则是检查型异常