Spring中事务用法示例及实现原理详解

时间:2022-10-29 14:01:03

前言

spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给hibernate或者jta等持久化机制所提供的相关平台框架的事务来实现。

关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性。对于spring事务,其最终还是在数据库层面实现的,而spring只是以一种比较优雅的方式对其进行封装支持。本文首先会通过一个简单的示例来讲解spring事务是如何使用的,然后会讲解spring是如何解析xml中的标签,并对事务进行支持的。

下面话不多说了,来一起看看详细的介绍吧

1. 使用示例

关于事务最简单的示例,就是其一致性,比如在整个事务执行过程中,如果任何一个位置报错了,那么都会导致事务回滚,回滚之后数据的状态将和事务执行之前完全一致。这里我们以用户数据为例,在插入用户数据的时候,如果程序报错了,那么插入的动作就会回滚。如下是用户的实体:

?
1
2
public class user { private long id; private string name; private int age;
 // getter, setter...}

如下是模拟插入用户数据的业务代码:

?
1
2
3
4
5
6
7
8
public interface userservice { void insert(user user);
}@service@transactionalpublic class userserviceimpl implements userservice { @autowired
 private jdbctemplate jdbctemplate; @override
 public void insert(user user) {
 jdbctemplate.update("insert into user (name, age) value (?, ?)",
  user.getname(), user.getage());
 }
}

在进行事务支持时,spring只需要使用者在需要事务支持的bean上使用@transactional注解即可,如果需要修改事务的隔离级别和传播特性的属性,则使用该注解中的属性进行指定。这里默认的隔离级别与各个数据库一致,比如mysql是repeatable read,而传播特性默认则为propagation.required,即只需要当前操作具有事务即可。如下是xml文件的配置:

?
1
2
3
4
5
6
7
8
<bean id="datasource" class="org.apache.commons.dbcp.basicdatasource" destroy-method="close">
 <property name="url" value="jdbc:mysql://localhost/test?useunicode=true"/>
 <property name="driverclassname" value="com.mysql.jdbc.driver"/>
 <property name="username" value="****"/>
 <property name="password" value="******"/></bean><bean id="jdbctemplate" class="org.springframework.jdbc.core.jdbctemplate">
 <property name="datasource" ref="datasource"/></bean><bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager">
 
 <property name="datasource" ref="datasource"/></bean><context:component-scan base-package="com.transaction"/><tx:annotation-driven/>

上述数据库配置用户按照各自的设置进行配置即可。可以看到,这里对于数据库的配置,主要包括四个方面:

  • datasource配置:设置当前应用所需要连接的数据库,包括链接,用户名,密码等;
  • jdbctemplate声明:封装了客户端调用数据库的方式,用户可以使用其他的方式,比如jparepository,mybatis等等;
  • transactionmanager配置:指定了事务的管理方式,这里使用的是datasourcetransactionmanager,对于不同的链接方式,也可以进行不同的配置,比如对于jparepository使用jpatransactionmanager,对于hibernate,使用hibernatetransactionmanager;
  • tx:annotation-driven:主要用于事务驱动,其会通过aop的方式声明一个为事务支持的advisor,通过该advisor和事务的相关配置进行事务相关操作。

按照上述配置,我们的事务功能即配置完成,如下是我们的驱动类程序:

?
1
2
3
4
5
6
7
8
9
10
11
12
public class transactionapp { @test
 public void testtransaction() {
 applicationcontext ac = new classpathxmlapplicationcontext("applicationcontext.xml");
 userservice userservice = ac.getbean(userservice.class);
 user user = getuser();
 userservice.insert(user);
 } private user getuser() {
 user user = new user();
 user.setname("mary");
 user.setage(27); return user;
 }
}

运行上述程序之后,可以看到数据库中成功新增了一条数据。这里如果我们将业务代码的插入语句之后手动抛出一个异常,那么,理论上插入语句是会回滚的。如下是修改后的service代码:

?
1
2
3
4
5
6
7
@service@transactionalpublic class userserviceimpl implements userservice { @autowired
 private jdbctemplate jdbctemplate; @override
 public void insert(user user) {
 jdbctemplate.update("insert into user (name, age) value (?, ?)",
  user.getname(), user.getage()); throw new runtimeexception();
 }
}

这里我们手动抛出了一个runtimeexception,再次运行上述程序之后我们发现数据库中是没有新增的数据的,这说明我们的事务在程序出错后是能够保证数据一致性的。

2. 标签解析

关于事务的实现原理,我们首先讲解spring是如何解析标签,并且封装相关bean的,后面我们会深入讲解spring是如何封装数据库事务的。

根据上面的示例,我们发现,其主要有三个部分:datasource,transactionmanager和tx:annotation-driven标签。这里前面两个部分主要是声明了两个bean,分别用于数据库连接的管理和事务的管理,而tx:annotation-driven才是spring事务的驱动。根据本人前面对spring自定义标签的讲解(spring自定义标签解析与实现),可以知道,这里tx:annotation-driven是一个自定义标签,我们根据其命名空间(www.springframework.org/schema/tx)在全局范围内搜索,可以找到其处理器指定文件spring.handlers,该文件内容如下:

http\://www.springframework.org/schema/tx=org.springframework.transaction.config.txnamespacehandler

这里也就是说tx:annotation-driven标签的解析在txnamespacehandler中,我们继续打开该文件可以看到起init()方法如下:

?
1
2
3
4
5
6
7
@overridepublic void init() {
 registerbeandefinitionparser("advice", new txadvicebeandefinitionparser());
 registerbeandefinitionparser("annotation-driven",
  new annotationdrivenbeandefinitionparser());
 registerbeandefinitionparser("jta-transaction-manager",
  new jtatransactionmanagerbeandefinitionparser());
}

可以看到,这里的annotation-driven是在annotationdrivenbeandefinitionparser中进行处理的,其parse()方法就是解析标签,并且注册相关bean的方法,如下是该方法的实现:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public beandefinition parse(element element, parsercontext parsercontext) { // 注册事务相关的监听器,如果某个方法标注了transactionaleventlistener注解,
 // 那么该方法就是一个事务事件触发方法,即发生某种事务事件后,将会根据该注解的设置,回调指定
 // 类型的方法。常见的事务事件有:事务执行前和事务完成(包括报错后的完成)后的事件。
 registertransactionaleventlistenerfactory(parsercontext);
 string mode = element.getattribute("mode"); // 获取当前事务驱动程序的模式,如果使用了aspectj模式,则会注册一个annotationtransactionaspect
 // 类型的bean,用户可以以aspectj的方式使用该bean对事务进行更多的配置
 if ("aspectj".equals(mode)) {
  registertransactionaspect(element, parsercontext);
 } else // 一般使用的是当前这种方式,这种方式将会在spring中注册三个bean,分别是
  // annotationtransactionattributesource,transactioninterceptor
  // 和beanfactorytransactionattributesourceadvisor,并通过aop的方式实现事务
  aopautoproxyconfigurer.configureautoproxycreator(element, parsercontext);
 } return null;
}

可以看到,对于事务的驱动,这里主要做了两件事:①注册事务相关的事件触发器,这些触发器由用户自行提供,在事务进行提交或事务完成时会触发相应的方法;②判断当前事务驱动的模式,有默认模式和aspectj模式,对于aspectj模式,spring会注册一个annotationtransactionaspect类型的bean,用于用户使用更亲近于aspectj的方式进行事务处理;对于默认模式,这里主要是声明了三个bean,最终通过aop的方式进行事务切入。下面我们看一下spring是如何注册这三个bean的,如下是aopautoproxyconfigurer.configureautoproxycreator的源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public static void configureautoproxycreator(element element,
  parsercontext parsercontext) { // 这个方法主要是在当前beanfactory中注册infrastructureadvisorautoproxycreator这个
 // bean,这个bean继承了abstractadvisorautoproxycreator,也就是其实现原理与我们前面
 // 讲解的spring aop的实现原理几乎一致
 aopnamespaceutils.registerautoproxycreatorifnecessary(parsercontext, element); // 这里的txadvisorbeanname就是我们最终要注册的bean,其类型就是下面注册的
 // beanfactorytransactionattributesourceadvisor,可以看到,其本质是一个
 // advisor类型的对象,因而spring aop会将其作为一个切面织入到指定的bean中
 string txadvisorbeanname = transactionmanagementconfigutils
  .transaction_advisor_bean_name; // 如果当前beanfactory中已经存在了目标bean,则不进行注册
 if (!parsercontext.getregistry().containsbeandefinition(txadvisorbeanname)) {
  object elesource = parsercontext.extractsource(element);  // 注册annotationtransactionattributesource,这个bean的主要作用是封装
  // @transactional注解中声明的各个属性
  rootbeandefinition sourcedef = new rootbeandefinition(  "org.springframework.transaction.annotation.annotationtransactionattributesource");
  sourcedef.setsource(elesource);
  sourcedef.setrole(beandefinition.role_infrastructure);
  string sourcename = parsercontext.getreadercontext()
   .registerwithgeneratedname(sourcedef);  // 注册transactioninterceptor类型的bean,并且将上面的封装属性的bean设置为其一个属性。
  // 这个bean本质上是一个advice(可查看其继承结构),spring aop使用advisor封装实现切面
  // 逻辑织入所需的所有属性,但真正的切面逻辑却是保存在其advice属性中的,也就是说这里的
  // transactioninterceptor才是真正封装了事务切面逻辑的bean
  rootbeandefinition interceptordef =
   new rootbeandefinition(transactioninterceptor.class);
  interceptordef.setsource(elesource);
  interceptordef.setrole(beandefinition.role_infrastructure);
  registertransactionmanager(element, interceptordef);
  interceptordef.getpropertyvalues().add("transactionattributesource",
   new runtimebeanreference(sourcename));
  string interceptorname = parsercontext.getreadercontext()
   .registerwithgeneratedname(interceptordef);  // 注册beanfactorytransactionattributesourceadvisor类型的bean,这个bean实现了
  // advisor接口,实际上就是封装了当前需要织入的切面的所有所需的属性
  rootbeandefinition advisordef =
   new rootbeandefinition(beanfactorytransactionattributesourceadvisor.class);
  advisordef.setsource(elesource);
  advisordef.setrole(beandefinition.role_infrastructure);
  advisordef.getpropertyvalues().add("transactionattributesource",
   new runtimebeanreference(sourcename));
  advisordef.getpropertyvalues().add("advicebeanname", interceptorname);  if (element.hasattribute("order")) {
   advisordef.getpropertyvalues().add("order", element.getattribute("order"));
  }
  parsercontext.getregistry().registerbeandefinition(txadvisorbeanname, advisordef);  // 将需要注册的bean封装到compositecomponentdefinition中,并且进行注册
 
  compositecomponentdefinition compositedef =
   new compositecomponentdefinition(element.gettagname(), elesource);
  compositedef.addnestedcomponent(   new beancomponentdefinition(sourcedef, sourcename));
  compositedef.addnestedcomponent(   new beancomponentdefinition(interceptordef, interceptorname));
  compositedef.addnestedcomponent(   new beancomponentdefinition(advisordef, txadvisorbeanname));
  parsercontext.registercomponent(compositedef);
 }
}

如此,spring事务相关的标签即解析完成,这里主要是声明了一个beanfactorytransactionattributesourceadvisor类型的bean到beanfactory中,其实际为advisor类型,这也是spring事务能够通过aop实现事务的根本原因。

3. 实现原理

关于spring事务的实现原理,这里需要抓住的就是,其是使用aop实现的,我们知道,aop在进行解析的时候,最终会生成一个adivsor对象,这个advisor对象中封装了切面织入所需要的所有信息,其中就包括最重要的两个部分就是pointcut和adivce属性。这里pointcut用于判断目标bean是否需要织入当前切面逻辑;advice则封装了需要织入的切面逻辑。如下是这三个部分的简要关系图:

Spring中事务用法示例及实现原理详解

同样的,对于spring事务,其既然是使用spring aop实现的,那么也同样会有这三个成员。我们这里我们只看到了注册的advisor和advice(即beanfactorytransactionattributesourceadvisor和transactioninterceptor),那么pointcut在哪里呢?这里我们查看beanfactorytransactionattributesourceadvisor的源码可以发现,其内部声明了一个transactionattributesourcepointcut类型的属性,并且直接在内部进行了实现,这就是我们需要找的pointcut。这里这三个对象对应的关系如下:

Spring中事务用法示例及实现原理详解

这样,用于实现spring事务的advisor,pointcut以及advice都已经找到了。关于这三个类的具体作用,我们这里进行整体的上的讲解,后面我们将会深入其内部讲解其是如何进行bean的过滤以及事务逻辑的织入的。

  •  beanfactorytransactionattributesourceadvisor:封装了实现事务所需的所有属性,包括pointcut,advice,transactionmanager以及一些其他的在transactional注解中声明的属性;
  •  transactionattributesourcepointcut:用于判断哪些bean需要织入当前的事务逻辑。这里可想而知,其判断的基本逻辑就是判断其方法或类声明上有没有使用@transactional注解,如果使用了就是需要织入事务逻辑的bean;
  •  transactioninterceptor:这个bean本质上是一个advice,其封装了当前需要织入目标bean的切面逻辑,也就是spring事务是如果借助于数据库事务来实现对目标方法的环绕的。

4. 小结

本文首先通过一个简单的例子讲解了spring事务是如何使用的,然后讲解了spring事务进行标签解析的时候做了哪些工作,最后讲解了spring事务是如何与spring aop进行一一对应的,并且是如何通过spring aop实现将事务逻辑织入目标bean的。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://my.oschina.net/zhangxufeng