一、事务的概念
事务(Transaction)是指逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
事务拥有四个特性:
- 原子性: 一组事务不可分割,要么全部发生,要么都不发生
- 一致性: 一组事务发生前后的数据量应保持一致
- 隔离性: 多个并发事务之间应相互隔离、互不干扰
- 持久性: 一组事务一旦执行成功,它带来的改变是永久性的
二、安全问题
如果隔离处理不当,可能导致以下三种读写问题发生:
- 脏读: A事务读取了B事务改写但还未提交的数据(如果B事务被回滚,那么这个数据是无效的)
- 不可重复读: 在同一事务中多次读取同一数据,获得的结果不同(在查询期间该数据被其它事务修改了)
- 幻读: 在同一事务中多次读取数据量,获得的数目不相等(在查询期间另一个事务插入了一些记录)
三、隔离级别
级别名称 | 原理 | 效果 |
---|---|---|
READ_UNCOMMITED | 无限制 | 无 |
READ_COMMITED | 允许在并发事务提交后读取 | 可防止脏读 |
REPEATABLE_READ | 对相同字段的多次读取均一致,除非被事务本身改变 | 可防止脏读、不可重复读 |
SERIALIZABLE | 依据ACID原则完全锁定事务中的数据,但速度最慢 | 可防止脏读、不可重复读、幻读 |
DEFAULT | 使用数据库默认的级别 | MySQL默认为REPEATABLE_READ |
四、传播行为
行为名称 | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果不存在就创建一个 |
PROPAGATION_REQUIRES_NEW | 如果有事务存在,挂起当前事务,创建一个新事务 |
PROPAGATION_NESTED | 如果当前事务存在,则嵌套事务执行 |
五、编程式事务管理
暂时省略,可参考其它博客
六、声明式事务管理
【模拟情景】假设现有a、b两个账户,它们各拥有1000元的资金,某时刻a账户向b账户转账300元,在转账的过程中突发断电。
初始数据库信息如下:
实现DAO层:
public class AccountDao extends JdbcDaoSupport {
/**
* 资金转出的方法
* @param out 发起方
* @param money 金额
*/
public void outMoney(String out, Double money) {
String sql = "update account set money = money - ? where name = ?";
this.getJdbcTemplate().update(sql, money, out);
}
/**
* 资金转入的方法
* @param in 接收方
* @param money 金额
*/
public void inMoney(String in, Double money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money, in);
}
}
实现Service层:
public class AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 转账的方法
* @param out 转出账号
* @param in 转入账号
* @param money 金额
*/
public void transfer(String out, String in, Double money) {
accountDao.outMoney(out, money);
int i = 1 / 0; // 制造异常来模拟断电情景
accountDao.inMoney(in, money);
}
}
完成相关的Spring配置后,执行accountService.transfer("a", "b", 300d);
如果没有进行事务管理,那么账户a将扣除300元,但账户b并不会收到这300元:
1. 基于TransactionProxyFactoryBean
XML的配置部分如下:
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置业务层的代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目标对象 -->
<property name="target" ref="accountService"/>
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 注入事务的属性 -->
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
注意:由于本方式为手动配置代理,此时应用id为accountServiceProxy的代理对象进行转账操作,不能再用原先的accountService对象
2. 基于AspectJ框架
XML的配置部分如下:
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pointcut1" expression="execution(* AccountService+.*(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
3. 基于注解
XML的配置部分如下:
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务的注解扫描 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在需要进行事务管理的类上添加注解:
@Transactional(propagation = Propagation.REQUIRED)
public class AccountService {
...
}
配置了事务管理之后,a扣款和b收款合为了一个不可分割的整体,即使a扣款执行后系统发生了中止,整体也会回滚到初始状态,不会再出现金额丢失的情况: