一、数据库事务
1. 事务的基本概念
事务(Transaction)是访问并可能更新数据库中各项数据项的一个执行单元(unit)。是恢复和并发控制的基本单位。
事物的ACID4个特性:原子性、一致性、隔离性、持久性
原子性(Automicity):一个事务是一个不可分割的工作单位,事务中的事情要么都做、要么都不做。
一致性(Consistency):事务必须是使数据库从一个一致性状态到另一个一致性状态,一致性与原子性是密切相关的。
隔离性(Isolation):一个事务的执行不能被其它事务所干扰。即一个事物内所修改及使用得数据对其它并行的事务来说是隔离的,并发执行的各个单位不能互相干扰。
持久性(Durability):持久性也成永久性,指一个事物一旦提交,它对数据库的改变应该是永久性的。
2. 数据库隔离级别
读未提交、读已提交、可重复读、串行化。
隔离级别 | 隔离级别的值 | 导致的问题 |
---|---|---|
Read-Uncommited | 0 | 导致脏读 |
Read-Committed | 1 | 避免脏读;允许不可重复读、幻读; |
Repeatable-Read | 2 | 避免脏读、不可重复读;允许幻读;- 类似 行锁 |
Serializable | 3 | 串行化,一个事务执行完才能执行下一个,避免脏读、不可重复读、幻读; 执行效率慢,使用时慎重;- 类似表锁 |
脏读:事务可以读取别的事务还未提交的数据。比如同一条数据,A事务先读取到然后做了修改但事务并未提交,此时B事务去读取数据会读取到A事务修改后的数据,此时A事务回滚,B事务也就读取到了脏数据。
不可重复读:针对单条数据。一个事物中发生了两次读操作,在第一次读操作和第二次读操作中,另一个事务对数据做了修改并提交了事务,则在第一个事务内两次读取到的数据时不同的。
幻读:针对多条数据。A事务执行批量更新操作,更新了一批数据;B事务在这个范围内新增了一条数据,此时A事务并不会对这条数据做修改。
数据库隔离级别设置的越高,越能保证数据的完整性和一致性,但是对并发的性能影响也越大。
大部分数据库的隔离级别默认是读已提交(Read-Commited),如SqlServer、Oracle。
少数数据库的默认隔离级别是可重复读(Repeatable-Read),如Mysql、InnoDB
二、Spirng事务
1. Spring事物的基础配置
先来看下常用的Spring事物的基础配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务通知属性 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
<tx:method name="remove*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
<tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
<tx:method name="login" propagation="NOT_SUPPORTED"/>
<tx:method name="query*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
<aop:aspect ref="dataSource">
<aop:pointcut id="transactionPointcut" expression="execution(public * com.gupaoedu..*.service..*Service.*(..))" />
</aop:aspect>
</aop:config>
Spring事物的管理基于Spring AOP实现,同一封装非功能性需求。
2. Spring事物的基本原理
Spring事物的本质其实是数据库对事务的支持,没有数据库的事务支持,Spring是无法提供事务功能的。对于底层使用JDBC操作数据库,用到事务的逻辑,可以参考下面的demo:
public void testJdbc() {
Connection conn = null;
Statement stmt = null;
try {
// 注册 JDBC 驱动
// Class.forName("com.mysql.jdbc.Driver");
// 1. 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "");
// 2. 开启手动提交事务
conn.setAutoCommit(false);
// 3. 执行CURD
stmt = conn.createStatement();
String sql = "update blog set name='测试' where bid = 1";
int i = stmt.executeUpdate(sql);
// 4.提交事务
conn.commit();
System.out.println(i);
} catch (Exception e) {
try {
// 4.回滚事务
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
// 5.释放资源
if (stmt != null) stmt.close();
} catch (SQLException se2) {
}
try {
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
}
Spring项目日常开发我们经常使用@Transactional
注解来来进行事物管理,而不再需要我们写步骤2和步骤4,那么Spring又是怎么实现的呢?
首先在程序启动时,Spring会根据我们配置的component-scan扫描包路径,解析相关的bean,这时候会查看这个类以及方法上的注解,针对使用了@Transactional的类和方法会重新生成代理,并根据@Transactional的相关参数进行配置注入,在代理类中字节码重组重新为原逻辑添加了开启事务,正常执行提交事务、异常回滚事务的逻辑。真正的数据库事务的提交和回滚则是由其内部机制,通过binlog或者redo log实现的。
3. Spring事务的传播属性
在使用@Transactional
注解时,默认传播属性为Propagation propagation() default Propagation.REQUIRED;
常量名称 | 解释 |
---|---|
Propagation.REQUIRED | 支持当前事务,如果当前没有事务就新建一个事务。这种是Spring最常见的传播属性,也是Spring默认的事务传播。 |
Propagation.REQUIRES_NEW | 使用一个新的事务,如果当前没有事务就新建一个事务,如果有事务则将当前事务挂起再新建事务。新的事物和原事务没有任何关系,是两个独立的事务,外层事务出错回滚之后不能回滚内层事务的结果,内层事务失败回滚外层捕获到也可以忽略回滚操作。 |
Propagation.SUPPORTS | 支持当前事务,如果当前没有事务则以非事务方式执行。 |
Propagation.MANDATORY | 支持当前事务,如果当前没有事务则抛出异常。必须在有外部事务的方法内执行。 |
Propagation.NOT_SUPPORTED | 不支持事务,如果当前有事务则将当前事务挂起再执行 |
Propagation.NEVER | 以非事务方式执行,如果当前存在事务则抛出异常。 |
Propagation.NESTED | 如果没有活动事务,则按REQUIRED方式执行。如果一个活动的事务存在,则运行在一个嵌套的事务中,内部事务是一个依赖于外部事务的子事务,此时外部事务拥有多个可以单独回滚的保存点。内部事务的回滚不会对外部事务造成影响。内部事务不能单独commit和rollback,会随外部事务一起commit或rollback。 |
NESTED 事务嵌套
NESTED和REQUIRES_NEW的却别在于,如果外部方法存在事务,当内部方法是NESTED时,会先在外部事务产生一个savepoint,然后开启一个子事务,子事务是依赖于外部事务的,外部事务commit子事务也随之commit,外部事务rollback子事务也会rollback,子事务内发生异常,会先回滚到之前的savepoint,外部事务捕获到子事务异常后可以根据真实需求来做后续处理;而REQUIRES_NEW在外部方法存在事务时,子方法会先将外部事务挂起,然后新建一个完全独立的事务来执行,内部事务和外部事务互不影响。
4. Spring中的隔离级别
在使用@Transactional
注解时,默认隔离级别为Isolation isolation() default Isolation.DEFAULT;
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void updateLog(BatchPreviousEntity bpe) {
...
}
常量名称 | 解释 |
---|---|
Isolation.DEFAULT | 是PlatformTransactionManager的默认隔离级别,使用数据库默认的事务隔离级别。另外4个与数据库的隔离级别相对应。 |
Isolation.READ_UNCOMMITTED | 读未提交 |
Isolation.READ_COMMITTED | 读已提交 |
Isolation.REPEATABLE_READ | 可重复读 |
Isolation.SERIALIZABLE | 串行化 |