SQL业务完成简介
首要咱们来了解下,最简略的业务是怎样完成的呢?以JDBC为例,当一个数据库Connection目标创立后,其会默许自动提交业务;每次履行SQL句子时,假如成功,就会向数据库自动提交,不能回滚。
经过调用setAutoCommit(false)办法能够撤销自动提交业务。比及一切的SQL句子都履行成功后,调用commit()办法提交业务。假如其间某个操作失败或出现反常时,则调用rollback()办法回滚业务。详细代码如下所示:
public void noTransaction() {
Connection connection = null;
String sql = "update account set balance=balance-100 where id=1";
String sql2 = "update account set balance=balance+100 where id=2";
//创立PreparedStatement目标
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();// 获取数据库衔接
connection.setAutoCommit(false);//业务开端
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();//履行第一个sql
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();//履行sql2
//提交业务
connection.commit();
} catch (SQLException e) {
//进行业务回滚,默许回滚到业务开端的地方
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
//封闭流
JDBCUtils.close(null, preparedStatement, connection);
}
}
将代码笼统成履行过程,首要有以下四步:
- 获取Mysql链接
- 履行SQL句子
- 提交SQL业务
- 存在反常则做Mysql的业务回滚。
能够发现,惯例情况下只要履行SQL句子的内容存在差异。假如能将相同部分抽取出来,接入方接入时只考虑SQL句子内容,就能够削减接入的成本。同时观察到抽取的部分处于履行SQL句子的前后,那么很自然的就能够想到两种解决方案:
1、在JAVA8中,供给了函数式编程。咱们能够即将履行的SQL句子封装成函数,作为入参传入并履行。
2、选用动态署理对履行SQL的前后做增强。
编程式业务
Spring中选用函数式编程完成的业务,被称为编程式业务。编程式业务的完成相对简略,首要由类TransactionTemplate担任完成。详细代码能够见如下所示:
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
//获取业务
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
//履行SQL句子内容
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
//反常回滚
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// 反常回滚
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
//提交业务
this.transactionManager.commit(status);
return result;
}
}
TransactionCallBack作为入参传入,其间就首要是咱们要履行的SQL句子内容。而其余部分能够看到,其实就和咱们前面所描绘的四步根本类似:
- 获取Mysql链接
- 履行SQL句子
- 提交SQL业务
- 存在反常则做Mysql的业务回滚。
声明式业务
在Spring中,选用AOP做增强逻辑的被称为声明式业务。相比起编程式业务,声明式业务相对复杂。因而,在了解声明式业务之前,咱们需求先简略了解一下Spring是如何支撑AOP(动态署理)。首要咱们知道,Spring中Bean的存在方法有以下几个阶段:
其间非常要害点就在BeanFactory。当咱们对一个Bean界说署理目标后,BeanFactory生成的就不会是单纯的Bean实例目标,而是Bean的动态署理。经过调用Bean的动态署理中的办法,来完成AOP。那么如何自界说自己的AOP呢?要完成AOP需求明确两个点:
1、需求在哪里做增强?(界说切点)
2、需求做什么样的增强逻辑?(界说增强逻辑)
关于这两点,Spring首要经过**业务署理办理装备类(ProxyTransactionManagementConfiguration)**进行完成。
从类图中能够看到,业务署理办理装备类首要界说了三个Bean目标:
- 注释业务特点源(AnnotationTransactionAttributeSource),其首要担任判别当时类是否为需求增强的类,即”哪里需求做增强”。
- 业务拦截器(TransactionInterceptor),该类首要担任对业务做链接获取、业务提交以及业务回滚。即”怎样做增强”。
- Bean工厂业务特点源辅导(BeanFactoryTransactionAttributeSourceAdvisor),这个与业务自身无关,首要是在Bean工厂生产Bean实例的时分,方便对Bean进行替换使用的。其间首要是担任将界说的切点和增强逻辑注入到Spring中。
这里咱们逐一来介绍这三个Bean目标。
注释业务特点源
“哪里需求做增强”,意味着类要具有判别是否需增强的才能。为此,注释业务特点源供给了一个要害的办法:isCandidateClass()。
但声明业务的注解必定不只一种。假如需求辨认一切包下的业务型注解,必定会需求屡次判别。因而,在注解业务特点源中,还保存了一组接口目标业务注释解析器(TransactionAnnotationParser),经过循环遍历这组业务注释解析器,就能够对不同结构注解进行处理。详细源码如下:
@Override
public boolean isCandidateClass(Class<?> targetClass) {
for (TransactionAnnotationParser parser : this.annotationParsers) {
if (parser.isCandidateClass(targetClass)) {
return true;
}
}
return false;
}
以SpringTransactionAnnotationParser注释解析器为例,其完成的isCandidateClass()办法判别类是否被@Transactional类注释了,假如是,那么该类便是潜在的候选类。
@Override
public boolean isCandidateClass(Class<?> targetClass) {
return AnnotationUtils.isCandidateClass(targetClass, Transactional.class);
}
依次类推,对@TransactionAttribute等其他结构的注释,咱们都能够选用这样办法完成。
业务拦截器
具有了判别哪些类需求履行业务的才能后,咱们还需求确定详细的增强逻辑是什么姿态的。而这便是业务拦截器首要功用。要完成这个功用,需求在对应办法被调用时,履行增强办法。
从类图首要能够看到,为了能够察觉到办法的调用,业务拦截器完成了办法拦截器接口(MethodInterceptor)的invoke办法,在invoke办法中先判别当时履行的办法归于哪个类,紧接着会用invokeWithinTransaction()对办法进行业务性的包装。其源码如下:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// 判别履行的办法归于哪个类
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
//再调用业务进行履行
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
首要逻辑放在invokeWithinTransaction()办法中。在该办法中,首要考虑了三类不同的编程方法的业务,分别是:呼应式业务(ReactiveTransactionManager)、回调优先型业务(CallbackPreferringPlatformTransactionManager)和非回调优先型业务(非CallbackPreferringPlatformTransactionManager)。
三者的差异首要在于:
1、呼应式编程常选用Mono或Flux完成,需求对两种方法挑选相应适配器做适配。
2、后两者从名字上能够看出差异,回调型优先的业务,会先履行回调再履行业务。而非回调优先型业务,则关注于业务的履行,至于回调的失败与否不需求影响业务的回滚。
虽然三者存在一些差异,但他们关于业务的完成其实是类似的,这里以非回调优先型业务为比如:
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
.......
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 创立业务
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 履行办法
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// 回滚处理 + 抛出反常终止履行
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
// 正常履行了业务,此时再履行回调
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 提交业务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
源码自身不复杂,能够看到也是四步:
- 获取Mysql链接信息
- 履行SQL句子
- 提交SQL业务
- 存在反常则做Mysql的业务回滚。
Bean工厂业务特点源辅导
关于Bean工厂业务特点源辅导,其首要担任用于界说切点和增强逻辑,并将这些业务的逻辑注册到Spring中用于完成。如下是Bean工厂业务特点源辅导的类图。
从类图上能够看到,其承继了AbstractPointcutAdvisor要害模版类,该类是Spring中用于界说切点和增强逻辑。经过指定PointCut和Advice,就能够完成自界说的增强逻辑。因而,Bean工厂业务特点源辅导只要将业务拦截器标记为增强逻辑,将注释业务特点源标记为切点,就能够让其在Spring中作为AOP收效。
经过这三者的协作:注释业务特点源标示了切点(说明我那些办法需求做增强);业务拦截器界说了要履行的增强逻辑(说明我对这些办法怎样做增强);Bean工厂业务特点源辅导则将切点和增强逻辑注入到Spring中使其收效。然后完成了Spring的声明式业务的内容。
业务多样性支撑
在前述内容中,咱们思考了SQL情况下如何完成业务。但有个问题,假如数据源换成Redission、换成分布式业务的API,代码还能快速复用么?简而言之,Spring是如何支撑数据源多样性?如何确保新数据源的快速接入?
对完成业务的流程做进一步笼统,不难发现一次业务中,结构需求关注的功用其实只要三个:
- 获取业务链接
- 提交业务
- 业务回滚
因而,对不同的数据源,都能够将其笼统成这三个才能。应用层只需求对这三个才能进行调用,就不会在因为基层数据源的差异而需求大幅度的改动。而这正与面向接口规划的思想不谋而合
为此,Spring专门规划了接口PlatformTransactionManager,其首要担任对外供给三个办法:getTransaction(definition)、commit(status)、rollback(status)。就用来笼统的上述的三个功用。由此一来,应用层的代码完成类(这里以TransactionTemplate为比如)就不再需求依靠于我的数据源究竟是JDBC、Redission仍是DataSource。面对笼统编程,然后削减了接入需求考虑不同类型所带来的成本。
总结
本文介绍了Spring中针对SQL业务完成的两种方法:编程式业务和声明式业务。同时介绍了关于多种不同的数据源,Spring在规划上的架构完成,期望对大家后续的开发规划有所协助。
参考文献
JDBC的业务与处理
详解 Spring 注解@Transactional业务控制原理