前言
对于一个应用而言,事务的使用基本是不可避免的。虽然Spring给我们提供了开箱即用的事务功能——@。
但是,自带的事务功能却也存在控制粒度不够的缺点。更糟糕的是,@在某些情况下就失效了。可能一些读者baidu/google一下解决办法后,失效的问题确实解决了。但是由于不了解底层的原理,这样的问题可能在今后的工作中往复出现。
本文就为大家揭开@下的秘密。
原生的事务管理
在没有Spring存在的时候,事务就已经诞生了。其实框架依赖的还是底层提供的能力,只不过它对这一过程的抽象和复用。
这里我们用底层的API来了解下事务管理的过程(JDBC为例):
// 获取mysql数据库连接
代码语言:javascript复制 Connection conn = DriverManager.getConnection("xxxx");
conn.setAutoCommit(false);
statement = conn.createStatement();
// 执行sql,返回结果集
resultSet = statement.executeQuery("xxxx");
conn.commit(); //提交
上面是一个原生操作事务的一个例子,这些过程也是Spring事务逃不开的,只不过在为了编程的效率让这一过程自动化或是透明化的你无法感知罢了。
而我们之后做的就是逐步还原这一自动化的过程。
Spring提供的事务API
Spring提供了很多关于事务的API。但是最为基本的就是anager、和。
事务管理器——anager
anager是事务管理器的顶层接口。事务的管理是受限于具体的数据源的(例如,JDBC对应的事务管理器就是),因此anager只规定了事务的基本操作:创建事务,提交事物和回滚事务。
public anager {
/**
代码语言:javascript复制 * 打开事务
*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
* 提交事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 回滚事务
*/
}
同时为了简化事务管理器的实现,Spring提供了一个抽象类,规定了事务管理器的基本框架,仅将依赖于具体平台的特性作为抽象方法留给子类实现。
事务状态——
事务状态是我对这个类的直译。其实我觉得这个类可以直接当作事务的超集来看(包含了事务对象,并且存储了事务的状态)。anager.()时创建的也正是这个对象。
这个对象的方法都和事务状态相关:
public , , {
/**
代码语言:javascript复制 * 是否有Savepoint Savepoint是当事务回滚时需要恢复的状态
*/
boolean hasSavepoint();
/**
* flush()操作和底层数据源有关,并非强制所有数据源都要支持
*/
@Override
}
此外,还从父接口中继承了其他方法,都归总在下方:
/**
代码语言:javascript复制 * 是否是新事务(或是其他事务的一部分)
*/
boolean isNewTransaction();
/**
* 设置rollback-only 表示之后需要回滚
*/
void setRollbackOnly();
/**
* 是否rollback-only
*/
boolean isRollbackOnly();
/**
* 判断该事务已经完成
*/
boolean isCompleted();
/**
* 创建一个Savepoint
*/
Object createSavepoint() throws TransactionException;
/**
* 回滚到指定Savepoint
*/
void rollbackToSavepoint(Object savepoint) throws TransactionException;
/**
* 释放Savepoint 当事务完成后,事务管理器基本上自动释放该事务所有的savepoint
*/
事务属性的定义——n
n表示一个事务的定义,将根据它规定的特性去开启事务。
事务的传播等级和隔离级别的常量同样定义在这个接口中。
/**
代码语言:javascript复制 * 返回事务的传播级别
*/
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
/**
* 返回事务的隔离级别
*/
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
}
/**
* 事务超时时间
*/
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
/**
* 是否为只读事务(只读事务在处理上能有一些优化)
*/
default boolean isReadOnly() {
return false;
}
/**
* 返回事务的名称
*/
@Nullable
default String getName() {
return null;
}
/**
* 默认的事务配置
*/
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
编程式使用Spring事务
有了上述这些API,就已经可以通过编程的方式实现Spring的事务控制了。
但是Spring官方建议不要直接使用anager这一偏低层的API来编程,而是使用和这两个偏向用户层的接口。
示例代码如下:
//设置事务的各种属性;可以猜测TransactionTemplate应该是实现了TransactionDefinition
代码语言:javascript复制 transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setTimeout(30000);
//执行事务 将业务逻辑封装在TransactionCallback中
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
//.... 业务代码
}
以上就是Spring事务最基本的原理。但是为什么这些过程对我们似乎都不可见呢?那是因为这些过程都通过AOP的方式被织入了我们的业务逻辑中。
所以,像要深入了解Spring事务原理,还需要了解AOP的原理。
AOP的原理
AOP的实现机制有两种:Proxy-based和-based。
前者是依赖动态代理的方式达到对代理类增强的目的。后者应该是通过字节码增强的方式达到增强的目的。
在Spring中,一般默认使用前者。之后也仅是针对前者进行分析。
而Spring声明AOP的方式也有两种,一种是通过声明Aspect,另一种是通过声明。
无论是哪种方式,都需要表达清楚你要进行增强的逻辑 (what)和你要增强的地方(where)。即,需要告诉Spring你要增强什么逻辑,并且对哪些Bean/哪些方法增强。
这里的what和where换成AOP中的概念分别就是对应Advice和。
因为事务是通过声明AOP的aop事务管理,因此本文也只针对的实现展开分析。
动态代理
既然是动态代理,那么必然存在被代理类(Target),代理类(Proxy),以及类被代理的过程(因为对用户而言,并不知道类被代理了)。
被代理的类
被代理类是最容易知道的,就是那些被的匹配(匹配或是)到的类。
代理的类
而代理类是在运行时直接创建的。通常有两种方式:
JDK的动态代理
CGLIB的动态代理
二者的区别是JDK动态代理是通过实现接口的方式(代理的对象为接口),因此只能代理接口中的方法。
而CGLIB动态代理是通过继承的方式,因此可以对对象中的方法进行代理,但是由于是继承关系,无法代理final的类和方法(无法继承),或是的方法(对子类不可见)。
创建代理及取代目标类的过程
创建代理及取代目标类主要是应用了Spring容器在获取Bean时留下的一个拓展点。
Spring在的时候,如果Bean还不存在会分三步去创建Bean:
实例化
填充属性
初始化
实例化通常是通过反射创建Bean对象的实例,此时得到的 Bean还只是一个空白对象。
填充属性主要是为这个Bean注入其他的Bean,实现自动装配。
而初始化则是让用户可以控制Bean的创建过程。
为Bean创建代理,并取代原有的Bean就是发生在初始化这一步,更具体的是在.()中。
动态代理也有可能在实例化之前直接创建代理,这种情况发生在.()中,此时的实例化过程不再是我们上文介绍的通过简单反射创建对象。
在众多的中有一类后置处理器就是专门用于创建代理的。例如,我们要介绍的。
看一下ator创建代理的流程:
先确认是否已经创建过代理对象(,避免对代理对象在进行代理)
如果没有,则考虑是否需要进行代理(通过)
如果是特殊的Bean 或者之前判断过不用创建代理的Bean则不创建代理
否则看是否有匹配的Advise(匹配方式就是上文介绍的通过或者可以直接匹配类)
如果找到了,说明需要创建代理,进入
首先会创建,这个工厂是用来创建的,而才是用来创建代理对象的。因为底层代理方式有两种(JDK动态代理和CGLIB,对应到的实现就是和xy)aop事务管理,所以这里使用了一个简单工厂的设计。会设置此次代理的属性,然后根据这些属性选择合适的代理方式,创建代理对象。
创建的对象会替换掉被代理对象(Target),被保存在.,因此当有其他Bean希望注入Target时,其实已经被注入了Proxy。
本文共 1943 个字数,平均阅读时长 ≈ 5分钟