数据库事务
事务必须满足ACID(原子性,一致性,隔离性,持久性) 这四种特性。
- 原子性(Atomicity):事务是不可分割的最小工作单位,事务的操作要么不做,要么全做。
- 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态。
- 隔离性(Isolation):并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响,这需要事务隔离级别来指定隔离性。
- 持久性(Durability):事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失。
实际项目开发过程中,事务一般都是并发进行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:
- 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的;
- 脏读:一个事务看到了另一个事务未提交的更新数据;
- 不可重复读:在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据;
- 幻读:一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。
为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:
- 未提交读(Read Uncommitted):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;
- 提交读(Read Committed):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不可能可能出现丢失更新、脏读,但可能出现不可重复读、幻读;
- 可重复读(Repeatable Read):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,可能可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
- 序列化(Serializable):最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读。
- 默认级别: 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是提交读。
隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。
事务的传播机制
在 spring的 TransactionDefinition接口中一共定义了六种事务传播属性:
1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。(默认配置)。
2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
3、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
4、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
编程式事务与声明式事务
编程式事务:指的是通过编码的方式,在dao层手动start transaction,commit等来实现事务。
Connection conn = null; UserTransaction tx = null; try { tx = getUserTransaction(); //1.获取事务 tx.begin(); //2.开启JTA事务 conn = getDataSource().getConnection(); //3.获取JDBC //4.声明SQL String sql = "select * from INFORMATION_SCHEMA.SYSTEM_TABLES"; PreparedStatement pstmt = conn.prepareStatement(sql);//5.预编译SQL ResultSet rs = pstmt.executeQuery(); //6.执行SQL process(rs); //7.处理结果集 closeResultSet(rs); //8.释放结果集 tx.commit(); //7.提交事务 } catch (Exception e) { tx.rollback(); //8.回滚事务 throw e; } finally { conn.close(); //关闭连接 }
声明式事务:指的是通过AOP的方式,在需要事务的方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法根据执行情况提交或者回滚事务。其最大的优点是不需要通过编码的方式来管理事务,这样就不需要在业务逻辑中掺杂事务的管理,一定程度上降低松耦合。只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。
声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
spring配置声明式事务非常简单
<!--配置事务管理器(mybatis采用的是JDBC的事务管理器)-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置基于注解的声明式事务,默认使用注解来管理事务行为-->
<tx:annotation-driven transaction-manager="transactionManager"/>
使用起来只需要在service层加上@Transactional 注解,就会在每个类方法注入事务。
@Service
@Transactional(value = "transactionManager",rollbackFor = Exception.class)
public class OrderFormServiceImpl implements OrderFormService {
@Autowired
private OrderFormMapper orderFormMapper;
@Override
public List<OrderForm> findNoSyncOrder() {
return orderFormMapper.findNoSyncOrder();
}
@Override
public void updateHaveGet(Long id) {
orderFormMapper.updateHaveGetByPrimaryKey(id);
}
}