既然讲spring事务源码分析,想必读者都知道什么是事务吧!包括事务四大特性ACID,4大隔离级别。笔者就不详细讲这些简单的知识了,简单列一下事务的概念。
事务的概念
- 原子性(atomicity)
一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性 - 一致性(consistency)
事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态 - 隔离性(isolation)
事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未授权读取,授权读取,可重复读取和串行化
读未提交(Read Uncommited),该隔离级别允许脏读取,其隔离级别最低;比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取就是未授权读取
授权读取也称为已提交读(Read Commited),授权读取只允许获取已经提交的数据。比如事务A和事务B同时进行,事务A进行+1操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10。另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样可以读取到20,即授权读取允许不可重复读取。
可重复读(Repeatable Read)
就是保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子中,可重复读取隔离级别能够保证事务B在第一次事务操作过程中,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一个事务操作)采用同样的查询方式,就可能读取到10或20;串行化
是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。
- 持久性(durability)
一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态。
进入正题
1. spring事务三大接口
- PlatformTransactionManager:平台事务管理器
- TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
- TransactionStatus:事务运行状态
PlatformTransactionManager
spring并不直接管理事务,而是提供了多种事务管理器,他们将事务的管理职责委托给Hibernate或JTA等持久化机制所提供的相关平台框架的事务来实现。通过这个接口,spring为各个平台如JDBC、Hibernate等提供了对应的事务管理器,但是事务的实现就是各平台自己的事情了。
public interface PlatformTransactionManager {
//根据事务定义TransactionDefinition,获取事务
TransactionStatus getTransaction(TransactionDefinition definition);
//提交事务
void commit(TransactionStatus status);
//回滚事务
void rollback(TransactionStatus status);
}
TransactionDefinition
该接口定义了5个方法以及一些表示事务属性的常量。
public interface TransactionDefinition {
/**
* 传播行为值
*/
//当前如果有事务,Spring就会使用该事务; 否则会开始一个新事务
int PROPAGATION_REQUIRED = 0;
//当前如果有事务,Spring就会使用该事务;否则不会开始一个新事务
int PROPAGATION_SUPPORTS = 1;
//当前如果有事务,Spring就会使用该事务;否则会抛出异常
int PROPAGATION_MANDATORY = 2;
//Spring总是开始一个新事务。如果当前有事务,则该事务挂起
int PROPAGATION_REQUIRES_NEW = 3;
//Spring不会执行事务中的代码。代码总是在非事务环境下执行,如果当前有事务,则该事务挂起
int PROPAGATION_NOT_SUPPORTED = 4;
//即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
int PROPAGATION_NEVER = 5;
//如果当前有事务,则在嵌套事务中执行。如果没有,那么执行情况与
//Transaction- Definition.PROPAGATION_REQUIRED一样
int PROPAGATION_NESTED = 6;
/**
* 隔离级别
*/
//默认隔离级别(对大多数数据库来说就是ISOLATION_ READ_COMMITTED)
int ISOLATION_DEFAULT = -1;
//最低的隔离级别。事实上我们不应该称其为隔离级别,因为在事务完成前,
//其他事务可以看到该事务所修改的数据。
//而在其他事务提交前,该事务也可以看到其他事务所做的修改
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
//大多数数据库的默认级别。在事务完成前,其他事务无法看到该事务所修改的数据。
//遗憾的是,在该事务提交后,你就可以查看其他事务插入或更新的数据。
// 这意味着在事务的不同点上,如果其他事务修改了数据,你就会看到不同的数据
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
//该隔离级别确保如果在事务中查询了某个数据集,你至少还能再次查询到相同的
// 数据集,即使其他事务修改了所查询的数据。
//然而如果其他事务插入了新数据,你就可以查询到该新插入的数据
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
//代价最大、可靠性最高的隔离级别,所有的事务都是按顺序一个接一个地执行
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
//默认事务的超时时间
int TIMEOUT_DEFAULT = -1;
//获取事务的传播行为
int getPropagationBehavior();
//获取事务的隔离级别
int getIsolationLevel();
//获取超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
//事务名称
@Nullable
String getName();
}
TransactionStatus
记录事务的状态,该接口定义了一组方法,用来获取或判断事务相应状态信息。PlatformTransactionManager.getTransaction(TransactionDefinition definition);方法返回一个TransactionStatus对象,该对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。
public interface TransactionStatus extends SavepointManager, Flushable {
//获取是否是新事务
boolean isNewTransaction();
//获取是否存在保存点
boolean hasSavepoint();
//设置事务回滚
void setRollbackOnly();
//获取是否回滚
boolean isRollbackOnly();
//刷新事务
@Override
void flush();
//获取事务是否完成
boolean isCompleted();
}
以上为前置知识,警告,前方高能!
2. 测试环境
老规矩,先祭出我们的测试代码!为源码讲解做准备。
配置类,注意,我们添加了@EnableTransactionManagement
注解
@ComponentScan("com.config.transaction")
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class Config {
}
数据源
@Configuration
@MapperScan("com.config.transaction")
public class DaoConfig {
private String driverClass = "com.mysql.jdbc.Driver";
private String username="root";
private String password="123456";
private String url="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong";
//注册数据源
@Bean
public DataSource getDataSorce() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
/**
* 获取sessionFactory
*/
@Bean
public SqlSessionFactory getSqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(getDataSorce());
return sqlSessionFactoryBean.getObject();
}
//注册jdbc事务管理器
@Bean
public TransactionManager getTrans() throws Exception {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(getDataSorce());
return transactionManager;
}
}
mybatis的Mapper
@Mapper
public interface DaoMapper {
@Insert("insert into user values(1,'name')")
public void add();
}
业务逻辑,注意,我们添加了@Transactional
事务注解
@Service
@Transactional(propagation= Propagation.REQUIRED,isolation = Isolation.DEFAULT)
public class ServiceImpl {
@Autowired
DaoMapper daoMapper;
public void add(){
daoMapper.add();
System.out.println("add");
}
public void del(){
System.out.println("del");
}
}
测试类
public class SpringTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(Config.class);
context.refresh();
ServiceImpl serviceImpl = context.getBean(ServiceImpl.class);
serviceImpl.add();
}
}
OK,运行上面的测试类,就能往数据库添加一条记录。
本文,就来讲讲事务的源码。注意,源码的讲解,需要你知道循环依赖源码和AOP源码,前面的博文我们详细讲解了,本文会直接定位到AOP源码处,不再讲解前置知识,详细参见《spring循环依赖源码讲解》和《AOP源码讲解》。
追根溯源,还是AOP
以debug模式运行上面的测试类:
我们在ServiceImpl业务类上加了
@Transactional
注解,调试代码发现,ServiceImpl类生成的实例被CGLIB动态代理了。我们去掉
@Transactional
注解再看看:此时的实例,没有被CGLIB动态代理。好,这就说明,事务是通过AOP的方式实现的。
所以,研究spring事务源码,就是研究事务是怎么通过AOP实现的,没问题吧?
3. 源码分析
先写到这,读者先去自行分析。没阅读过循环依赖和AOP源码的,赶紧回去看,不然你听不懂。