本专题大纲以下:java
事务专题大纲mysql
「对于专题大纲我又作了调整哈,主要是但愿专题的内容可以更丰富,更加详细」,原本是想在源码分析的文章中附带讲一讲事务使用中的问题,这两天想了想仍是单独写一篇并做为事务专题的收尾篇,也是我Spring源码专题的收尾篇。程序员
本文大纲以下:spring
Spring事务应用大纲sql
在看这篇文章,以及下篇源码分析的文章我但愿你对Spring AOP以及有充分的了解,否则一些细节问题你可能看不明白,关于Spring AOP若是你能看完这三篇文章基本上就没什么问题了数据库
Spring官网阅读(十八)AOP的核心概念apache
Spring中AOP相关的API及源码解析,原来AOP是这样子的编程
Spring提供了两种编程式事务管理的方法安全
若是是使用的是命令式编程,Spring推荐使用TransactionTemplate 来完成编程式事务管理,若是是响应式编程,那么使用TransactionalOperator更加合适。session
TransactionTemplate
使用示例(我这里直接用的官网提供的例子了)
public class SimpleService implements Service { private final TransactionTemplate transactionTemplate; // 使用构造对transactionTemplate进行初始化 // 须要提供一个transactionManager public SimpleService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { // 这里实现本身的相关业务逻辑 updateOperation1(); return resultOfUpdateOperation2(); } }); } }
在上面的例子中,咱们显示的使用了TransactionTemplate来完成事务管理,经过实现TransactionCallback接口并在其doInTransaction方法中完成了咱们对业务的处理。咱们能够大概看下TransactionTemplate的execute方法的实现:
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 { // 1.经过事务管理器开启事务 TransactionStatus status = this.transactionManager.getTransaction(this); T result; try { // 2.执行传入的业务逻辑 result = action.doInTransaction(status); } catch (RuntimeException | Error ex) { // 3.出现异常,进行回滚 rollbackOnException(status, ex); throw ex; } catch (Throwable ex) { // 3.出现异常,进行回滚 rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } // 4.正常执行完成的话,提交事务 this.transactionManager.commit(status); return result; } }
这些方法具体的实现咱们暂且不看,后续进行源码分析时都会详细介绍,之因此将这个代码贴出来是让你们更好的理解TransactionTemplate的工做机制:实际上就是经过一个TransactionCallback封装了业务逻辑,而后TransactionTemplate会在事务的上下文中调用。
在上面的例子中doInTransaction是有返回值的,而实际上有时候并不须要返回值,这种状况下,咱们可使用TransactionCallbackWithoutResult提代TransactionCallback。
transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } });
❝
实际上咱们还能够经过TransactionTemplate指定事务的属性,例如隔离级别、超时时间、传播行为等等
TransactionTemplate是线程安全的,咱们能够全局配置一个TransactionTemplate,而后全部的类都共享这个TransactionTemplate。可是,若是某个类须要特殊的事务配置,例如须要定制隔离级别,那么咱们就有必要去建立不一样的TransactionTemplate。
❞
TransactionOperator
❝
TransactionOperator适用于响应式编程的状况,这里就不作详细介绍了
❞
TransactionManager
实际上TransactionTemplate内部也是使用TransactionManager来完成事务管理的,咱们以前也看过它的execute方法的实现了,其实内部就是调用了TransactionManager的方法,实际上就是分为这么几步
这里我仍是直接用官网给出的例子
// 定义事务 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // txManager,事务管理器 // 经过事务管理器开启一个事务 TransactionStatus status = txManager.getTransaction(def); try { // 完成本身的业务逻辑 } catch (MyException ex) { // 出现异常,进行回滚 txManager.rollback(status); throw ex; } // 正常执行完成,提交事务 txManager.commit(status);
咱们在后边的源码分析中其实重点分析的也就是TransactionManager的源码。
在对编程式事务有必定了解以后咱们会发现,编程式事务存在下面几个问题:
如何解决呢?这就要用到申明式事务了,实现申明式事务通常有两种方式
申明式事务事务的实现原理以下(图片来源于官网):
实现原理
「实际上就是结合了APO自动代理跟事务相关API」。经过开启AOP自动代理并向容器中注册了事务须要的通知(Transaction Advisor),在Transaction Advisor调用了事务相关API,其实内部也是调用了TransactionManager的方法。
基于XML配置这种方式就不讲了,笔者近两年时间没用过XML配置,咱们主要就看看经过注解方式来实现申明式事务。主要涉及到两个核心注解
@EnableTransactionManagement这个注解主要有两个做用,其一是,开启AOP自动代理,其二是,添加事务须要用到的通知(Transaction Advisor),若是你对AOP有必定了解的话那你应该知道一个Advisor实际上就是一个绑定了切点(Pointcut)的通知(Advice),经过@EnableTransactionManagement这个注解导入的Advisor所绑定的切点就是经过@Transactional来定义的。
申明式事务的例子我这里就省去了,我相信没几我的不会用吧.....
Spring事务抽象的关键就是事务策略的概念,事务策略是经过TransactionManager接口定义的。TransactionManager自己只是一个标记接口,它有两个直接子接口
❝
关于响应式跟命令式编程均可以单独写一篇文章了,本文重点不是讨论这两种编程模型,能够认为日常咱们使用的都是命令式编程
❞
PlatformTransactionManager
PlatformTransactionManager接口定义
public interface PlatformTransactionManager extends TransactionManager { // 开启事务 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交事务 void commit(TransactionStatus status) throws TransactionException; // 回滚事务 void rollback(TransactionStatus status) throws TransactionException; }
PlatformTransactionManager是命令式编程模型下Spring事务机制的中心接口,定义了完成一个事务必须的三个步骤,也就是说定义了事务实现的规范
一般来讲,咱们不会直接实现这个接口,而是经过继承AbstractPlatformTransactionManager,这个类是一个抽象类,主要用做事务管理的模板,这个抽象类已经实现了事务的传播行为以及跟事务相关的同步管理。
回头看接口中定义的三个方法,首先是开启事务的方法,从方法签名上来看,其做用就是经过一个TransactionDefinition来获取一个TransactionStatus类型的对象。为了更好的理解Spring中事务的抽象咱们有必要了解下这两个接口
TransactionDefinition
接口定义以下:
public interface TransactionDefinition { // 定义了7中事务的传播机制 int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; // 4种隔离级别,-1表明的是使用数据库默认的隔离级别 // 好比在MySQL下,使用的就是ISOLATION_REPEATABLE_READ(可重复读) int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; int ISOLATION_READ_COMMITTED = 2; int ISOLATION_REPEATABLE_READ = 4; int ISOLATION_SERIALIZABLE = 8; // 事务的超时时间,默认不限制时间 int TIMEOUT_DEFAULT = -1; // 提供了对上面三个属性的get方法 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; } // 返回一个只读的TransactionDefinition // 只对属性提供了getter方法,全部属性都是接口中定义的默认值 static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; } }
从这个接口的名字上咱们也能知道,它的主要完成了对事务定义的抽象,这些定义有些是数据库层面自己就有的,例如隔离级别、是否只读、超时时间、名称。也有些是Spring赋予的,例如事务的传播机制。Spring中一共定义了7种事务的传播机制
关于事务的传播在源码分析的文章中我会重点介绍,如今你们留个印象便可。
咱们在使用申明式事务的时候,会经过@Transactional这个注解去申明某个方法须要进行事务管理,在@Transactional中能够定义事务的属性,这些属性实际上就会被封装到一个TransactionDefinition中,固然封装的时候确定不是直接使用的接口,而是这个接口的一个实现类RuleBasedTransactionAttribute。RuleBasedTransactionAttribute,该类的继承关系以下:
RuleBasedTransactionAttribute
// 默认的传播机制为required,没有事务新建一个事务 // 有事务的话加入当前事务 private int propagationBehavior = PROPAGATION_REQUIRED; // 隔离级别跟数据库默认的隔离级别一直 private int isolationLevel = ISOLATION_DEFAULT; // 默认为-1,不设置超时时间 private int timeout = TIMEOUT_DEFAULT; // 默认不是只读的 private boolean readOnly = false;
`DefaultTransactionDefinition
,新增了两个事务的属性// 用于指定事务使用的事务管理器的名称 String getQualifier(); // 指定在出现哪一种异常时才进行回滚 boolean rollbackOn(Throwable ex);
// 抛出RuntimeException/Error才进行回滚 public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); }
TransactionStatus
这个接口主要用于描述Spring事务的状态,其继承关系以下:
TransactionStatus
// 判断当前事务是不是一个新的事务 // 不是一个新事务的话,那么须要加入到已经存在的事务中 boolean isNewTransaction(); // 事务是否被标记成RollbackOnly // 若是被标记成了RollbackOnly,意味着事务只能被回滚 void setRollbackOnly(); boolean isRollbackOnly(); // 是否事务完成,回滚或提交都意味着事务完成了 boolean isCompleted();
// 建立保存点 Object createSavepoint() throws TransactionException; // 回滚到指定保存点 void rollbackToSavepoint(Object savepoint) throws TransactionException; // 移除回滚点 void releaseSavepoint(Object savepoint) throws TransactionException;
//用于判断当前事务是否设置了保存点 boolean hasSavepoint(); // 这个方法复写了父接口Flushable中的方法 // 主要用于刷新会话 // 对于Hibernate/jpa而言就是调用了其session/entityManager的flush方法 void flush();
❝
小总结:
经过上面的分析咱们会发现,TransactionDefinition的主要做用是给出一份事务属性的定义,而后事务管理器根据给出的定义来建立事务,TransactionStatus主要是用来描述建立后的事务的状态
❞
在对TransactionDefinition跟TransactionStatus有必定了解后,咱们再回到PlatformTransactionManager接口自己,PlatformTransactionManager做为事务管理器的基础接口只是定义管理一个事务必须的三个方法:开启事务,提交事务,回滚事务,接口仅仅是定义了规范而已,真正作事的仍是要依赖它的实现类,因此咱们来看看它的继承关系
PlatformTransactionManager的实现类
PlatformTransactionManager
image-20200806194650050
关于事务管理器的详细代码分析放到下篇文章,本文对其有个大概了解便可。
Spring中事务相关的同步机制能够分为两类
什么是资源的同步呢?在一个事务中咱们每每会一次执行多个SQL(若是是单条的SQL实际上没有必要开启事务),为了保证事务全部的SQL都可以使用一个数据库链接,这个时候咱们须要将数据库链接跟事务进行同步,这个时候数据库链接就是跟这个事务同步的一个资源。
那什么又是行为的同步呢?仍是以数据库链接为例子,在事务开启以前咱们须要先获取一个数据库链接,一样的在事务提交时咱们须要将链接关闭(不必定是真正的关闭,若是是链接池只是归还到链接池中),这个时候关闭链接这个行为也须要跟事务进行同步
那么Spring是如何来管理同步的呢?一样的,Spring也提供了一个同步管理器TransactionSynchronizationManager,这是一个抽象类,其中全部的方法都是静态的,而且全部的方法都是围绕它所申明的几个静态常量字段,以下:
// 这就是同步的资源,Spring就是使用这个完成了链接的同步 private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); // TransactionSynchronization完成了行为的同步 // 关于TransactionSynchronization在后文进行分析 private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); // 事务的名称 private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); // 事务是否被标记成只读 private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status"); // 事物的隔离级别 private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level"); // 是否真实开启了事务 private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
能够看到全部的同步都是经过ThreadLocal实现的,对于ThreadLocal本文不作详细分析,若是对ThreadLocal还不了解的同窗也没有关系,对于本文而言你只须要知道ThreadLocal能将资源跟当前线程绑定便可,例如ThreadLocal<Map<Object, Object>> resources这个属性就表明要将一个map绑定到当前线程,它提供了set跟get方法,分别用于将属性绑定到线程上以及获取线程上绑定的属性。
上面的几个变量中除了synchronizations以外其他的应该都很好理解,synchronizations中绑定的是一个TransactionSynchronization的集合,那么这个TransactionSynchronization有什么用呢?咱们来看看它的接口定义
public interface TransactionSynchronization extends Flushable { // 事务完成的状态 // 0 提交 // 1 回滚 // 2 异常状态,例如在事务执行时出现异常,而后回滚,回滚时又出现异常 // 就会被标记成状态2 int STATUS_COMMITTED = 0; int STATUS_ROLLED_BACK = 1; int STATUS_UNKNOWN = 2; // 咱们绑定的这些TransactionSynchronization须要跟事务同步 // 1.若是事务挂起,咱们须要将其挂起 // 2.若是事务恢复,咱们须要将其恢复 default void suspend() { } default void resume() { } @Override default void flush() { } // 在事务执行过程当中,提供的一些回调方法 default void beforeCommit(boolean readOnly) { } default void beforeCompletion() { } default void afterCommit() { } default void afterCompletion(int status) { } }
能够看到这个接口就是定义了一些方法,这些个方法能够在事务达到不一样阶段后执行,能够认为定义了事务执行过程的一些回调行为,这就是我以前说的行为的同步。
本文的最后一部分但愿你们模拟一下Spring事务的实现,咱们利用现有的AOP来实现事务的管理。数据库访问咱们直接使用jdbc,在模拟以前咱们先明确两点
咱们先说第一个问题,由于是咱们本身模拟,因此关于切点的定义咱们就设置的尽可能简单一些,不妨就直接指定某个包下的全部类。对于第二个问题,咱们也不作的过于复杂,在方法执行前开启事务,在方法执行后提交事务并关闭链接,因此咱们须要定义一个环绕通知。同时,咱们也须要将链接跟事务同步,保证事务中的全部SQL共用一个事务是实现事务管理的必要条件。基于此,咱们开始编写代码
咱们只须要引入Spring相关的依赖跟JDBC相关依赖便可,该项目仅仅是一个Spring环境下的Java项目,没有Web依赖,也不是SpringBoot项目,项目结构以下:
POM文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dmz.framework</groupId> <artifactId>mybatis</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> </dependencies> </project>
配置类:
// 开启AOP跟扫描组件便可 @EnableAspectJAutoProxy @ComponentScan("com.dmz.mybatis.tx_demo") public class Config { }
完成事务管理的核心类:
public class TransactionUtil { public static final ThreadLocal<Connection> synchronousConnection = new ThreadLocal<Connection>(); private TransactionUtil() { } public static Connection startTransaction() { Connection connection = synchronousConnection.get(); if (connection == null) { try { // 这里替换成你本身的链接地址便可 connection = DriverManager .getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8", "root", "123"); synchronousConnection.set(connection); connection.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } return connection; } public static int execute(String sql, Object... args) { Connection connection = startTransaction(); try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) { if (args != null) { for (int i = 1; i < args.length + 1; i++) { preparedStatement.setObject(i, args[i - 1]); } } return preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } return 0; } public static void commit() { try (Connection connection = synchronousConnection.get()) { connection.commit(); synchronousConnection.remove(); } catch (SQLException e) { e.printStackTrace(); } } public static void rollback() { try (Connection connection = synchronousConnection.get()) { connection.rollback(); synchronousConnection.remove(); } catch (SQLException e) { e.printStackTrace(); } } }
实际须要事务管理的类
@Component public class UserService { public void saveUser() { TransactionUtil.execute ("INSERT INTO `test`.`user`(`id`, `name`) VALUES (?, ?)", 100, "dmz"); // 测试回滚 // throw new RuntimeException(); } }
切面:
@Component @Aspect public class TxAspect { @Pointcut("execution(public * com.dmz.mybatis.tx_demo.service..*.*(..))") private void pointcut() { } @Around("pointcut()") public Object around(JoinPoint joinPoint) throws Throwable { // 在方法执行前开启事务 TransactionUtil.startTransaction(); // 执行业务逻辑 Object proceed = null; try { ProceedingJoinPoint method = (ProceedingJoinPoint) joinPoint; proceed = method.proceed(); } catch (Throwable throwable) { // 出现异常进行回滚 TransactionUtil.rollback(); return proceed; } // 方法执行完成后提交事务 TransactionUtil.commit(); return proceed; } }
用于测试的主函数:
public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class); UserService userService = ac.getBean(UserService.class); userService.saveUser(); } }
具体的测试过程跟测试结果我就不放了,你们把代码拷贝过去自行测试就行了
本文主要介绍了Spring中的事务相关内容,对Spring中的事务抽象机制作了介绍,主要是为了让你们在接下来一篇源码文章中能减轻负担,但愿你们能够根据本身理解动手模拟下Spring中事务的实现哦,当你本身去实现的时候确定会碰到一系列的问题,而后带着这些问题看源码你才能知道Spring为何要作这些事情!
我叫DMZ,一个但愿慢慢进步的小菜鸟~!
往期精选
程序员DMZ点赞、转发、在看,多谢多谢!喜欢做者