我尽量用最白话的词来阐述我所认识的事务,有误欢迎指导交流。
四个特性
- 原子性:要么都变,要么都不变。
- 一致性:变后和预期一致。
- 隔离性:事务之间影响范围。
- 持久性:操作后数据落库持久化。
隔离级别
上面说到四个特性有一个隔离性,但这个隔离性并不是固定的,而是可配置(当然每个数据库有默认值,比如mysql默认是可重复读,Oracle是读提交)。
了解隔离级别前,先熟悉三个概念:脏读、重复读、幻读。
脏读:读到另外一个事务还没有提交的数据。
一个事务修改了用户数据,但是还没有提交事务,另外事务查询用户数据能读到此类数据。
重复读:读到了已经提交的事务的数据,针对数据的变更。
一个事务修改了用户数据,并且提交事务,另外一个事务能查询到所提交的数据。
幻读:读到了已经提交的事务的数据,针对数据的新增。
一个事务新增了用户数据,并且提交事务,另外一个事务能查询到所提交数据,出现两次查询不一致的情况。
隔离级别的几种
- 不支持事务
- 读未提交【read_uncommited】
- 读提交【read_committed】
- 可重复读【repeatable_read】
- 序列化【serializable】
下面用表格来呈现隔离级别和脏读幻读重复读出现的情况。
隔离级别 | 脏读 | 重复读 | 幻读 |
---|---|---|---|
不支持事务 | √ | √ | √ |
读未提交 | √ | √ | √ |
读提交 | - | √ | √ |
可重复读 | - | - | √ |
序列化 | - | - | - |
打钩表示会出现。
怎么用事务
jdbc
默认会开启事务,在单条执行后提交事务。
当然可以关闭自动提交。使用commit()和rollback()来控制事务提交和回滚。
Connection conn = DriverManager.getConnection("url","name","passwd");
try {
//关闭自动提交事务
conn.setAutoCommit(false);
stme = conn.createStatement();
stmt.excuteUpdate("执行sql语句");
conn.commit();
} catch(Exception e) {
e.printStackTrace();
conn.rollback();
} fianlly {
if(stmt != null)
stmt.close();
if(conn != null)
conn.close();
}
jta
此处不阐述使用例子。
jdbc就是针对单体服务、单个数据库的事务控制,那么碰到多个服务多个数据库jdbc就处理不了。这时候就需要jta,其提供的api能控制多个服务间的事务开始、回滚、提交等。
spring
上面两个说是编程式事务,那么spring出现带来了声明式事务。
同时也带来事务传播机制。
声明式事务:
spring声明式事务是在AOP基础上进行,在目标方法前创建事务,执行之后收回并根据异常确定是否回滚。
spring声明式事务,可以基于多种形式,现在主要使用的是基于@Transactional的声明式事务,也就是基于注解。
spring事务传播机制:
有7中传播行为
propagetion_required 支持当前事务,如无事务,新建一个
propagetion_supports 支持当前事务,如无事务,不使用
propagetion_mandatory 支持当前事务,如无事务,抛出异常
propagetion_requires_new 如有事务存在,挂起当前事务,新建一个事务(无论当前有无,都会新建一个事务)
propagetion_not_supported 支持非事务方式运行,如有事务,挂起
propagetion_never 以非事务方式运行,有事务则抛出异常
propagetion_nested 嵌套事务(结合保存点使用,当内嵌回滚,外部事务不受影响)
简单记忆:需要事务、支持或不支持事务、需要或不需要事务、新事务或内嵌事务。(需要不需要会报错,支持不支持会挂起,新事务分开提交不影响外层,内嵌事务同时提交)
spring事务不生效的几种原因:
- 检查事务是否开启
- 检查@Transactional注解是否是接口,是否public修饰
- 检查数据库引擎(mysql的myisam就是不支持事务的)
- spring是否扫描到你的包
- 异常是否是检查异常
- 是否是同一个类中调用(比如A类中的a()调用b()方法,这样事务是不会生效的,spring动态代理的逻辑了解下)