一文就够!源码级别讲解Spring事务——声明式事务和编程式事务

spring事务-声明式事务和编程式事务


1.数据库事务特性


我们都知道mysql数据库的事务四大特性ACID



2.脏读、幻读、不可重复读


脏读:? 脏读是事务2读取到了事务1未提交的数据


不可重复读:? 事务1对数据读取后,事务2对其数据进行修改,事务1再次读取数据时,得到和第一次读取不同的数据。


幻读? 事务1对某数据读取后,事务2对其数据进行删除或者添加记录,当事务1再次读取时发现记录数减少或者增加了


3.mysql事务隔离级别


以下是各个隔离级别所对脏读、幻读、不可重复读的支持情况,默认下mysql的隔离级别是可重复读。


隔离级别

脏读

不可重复读

幻读

未提交读(READ UNCOMMITTED)

存在

存在

存在

已提交读(READ COMMITTED)

不存在

存在

存在

可重复读(REPEATABLE READ)

不存在

不存在

存在

可串行化(SERIALIZABLE)

不存在

不存在

不存在


4.未提交读产生脏读演示案例


我们在连接1中,设置当前会话事务隔离级为未提交读,之后开启事务向数据库里面插入了一条记录,并不手动执行commit 语句,此时事务处于未提交状态:


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


此时在连接2中设置隔离就级别为未提交读,并查询数据表,此时结果为:


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


此时可以看到连接2是可以查询到连接1为提交事务的数据,所以此时就会产生脏读


4.已提交读会不会产生脏读?


我们将数据库的隔离级别修改为已提交读,并开启事务,向表中插入一条数据,不执行commit:


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


此时我们再打开连接2,查询:


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


此时我们可以看到连接2是读取不到连接1未提交的数据的。


小结:mysql提供了4种隔离级别,每个级别对脏读、不可重复读、幻读 这三者提供了不同的解决,这4种隔离级别自上而下(上表中),能解决问题的能力越强。在上表中最安全的级别则是 可串行化,但是mysql为什么默认的隔离级别不是它呢,原因是 因为隔离级别越高,其并发性能越低。


5.JDBC事务控制


JDBC 是 Java 语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了查询和更新数据库中数据的方法。JDBC 也是 Sun Microsystems 的商标(现在属于 Oracle),是面向关系型数据库的。


最原始的JDBC数据库连接方式,如下所示:


public void updateCoffeeSales()throws SQLException {
    try {
        //获取连接
        Connection conn = DriverManager.getConnection(DB_URL,USER,PASS);
        //关闭自动提交
        con.setAutoCommit(false);
        //DML数据库操作
        ...
        //手动提交事务
         con.commit();
         
        } catch (SQLException e ) {
            //出现异常回滚
            con.rollback();
       
        } finally {
        //关闭连接等操作
    }
}


在本段代码块中,在JDBC处理事务时我们需要手动地进行事务提交,当发生异常时调用连接对象的回滚方法进行回滚操作。在此也可见事务最基本的依赖是数据库连接对象,


6.Spring的事务支持


6.1 事务管理器


spring并不自己实现事务的管理,而是给出来一个抽象的事务管理接口或者超类,需要其他db框架来具体实现。 Spring提供的抽象事务管理器接口:


// Spring提供的事务管理器顶级接口
public interface TransactionManager {

}
--------------
public interface PlatformTransactionManager extends TransactionManager {
        // 根据事务定义获取事务状态
    	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;
    	//提交事务
    	void commit(TransactionStatus status) throws TransactionException;
    	//回滚事务
    	void rollback(TransactionStatus status) throws TransactionException;
}



一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


由以上两张图可以见得DataSourceTransactionManager 管理器所在的包为Spring-jdbc中,是一个第三方的DB框架。 spring并不自己实现事务的管理,而是给出来一个抽象的事务管理接口或者超类,需要其他db框架来具体实现


6.2关键名词解释:



public interface PlatformTransactionManager extends TransactionManager {

    //返回当前活动的事务或创建一个新的事务
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;

	//提交给定事务
	void commit(TransactionStatus status) throws TransactionException;

	//回滚指定事务
	void rollback(TransactionStatus status) throws TransactionException;

}



public interface TransactionDefinition {
//------事务传播行为------

//支持当前事务; 如果不存在,则创建一个新的。  
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;

//------事务隔离级别------
//使用底层数据存储的默认隔离级别。
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;

// 在接口中提供默认的事务传播行为方法
default int getPropagationBehavior() {
		return PROPAGATION_REQUIRED;
	}
//默认事务隔离级别
default int getIsolationLevel() {
		return ISOLATION_DEFAULT;
	}

}



public interface SavepointManager {
    //创建一个新的保存点。可以回滚到特定的保存点
	Object createSavepoint() throws TransactionException;
	//回滚到给定的保存点。
	void rollbackToSavepoint(Object savepoint) throws TransactionException;
	//显式释放给定的保存点。
	void releaseSavepoint(Object savepoint) throws TransactionException;
	}



public interface TransactionExecution {
	 //返回当前事务是否为新事务;
	boolean isNewTransaction();
	 //设置事务仅回滚
	void setRollbackOnly();
	//返回事务是否已标记为仅回滚
	boolean isRollbackOnly();
    //返回该事务是否完成,
	boolean isCompleted();

}



public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable 
    // 事务内部是否有保存结点
	boolean hasSavepoint();
	// 刷新
	@Override
	void flush();
}


6.2Spring编程式事务


Spring中推荐使用TransactionTemplate接口来实现编程式事务:


@RequestMapping("test")
public class TestTx {
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private IResourceAdminService iResourceAdminService;
    @GetMapping()
    public void test(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    ResourceAdmin resourceAdmin = new ResourceAdmin();
                    resourceAdmin.setName("张三");
                    resourceAdmin.setPassword("123456");
                    // 数据库保存操作
                    boolean save = iResourceAdminService.save(resourceAdmin);
                    if(save){
                        log.info("数据库保存1成功");
                    }
                    // 模拟异常
                    int i=5/0;
                }catch (Exception e){
                    e.printStackTrace();
                    status.setRollbackOnly();
                    log.error("发生异常回滚事务");
                }
            }
        });
    }
}


6.3浅淡TransactionTemplate源码中提供的excute方法:


	@Override
	@Nullable
	public  T execute(TransactionCallback 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) {
				// Transactional code threw application exception -> rollback
			//3.异常回滚
				rollbackOnException(status, ex);
				throw ex;
			}
			catch (Throwable ex) {
				// Transactional code threw unexpected exception -> rollback
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			}
			//4.事务管理器提交事务
			this.transactionManager.commit(status);
			return result;
		}
	}


excute方法的参数是一个函数式接口:


doInTransaction(TransactionStatus status)方法是我们需要重写的


@FunctionalInterface
public interface TransactionCallback {
    //当使用excute方法时需要是重写这个方法
	@Nullable
	T doInTransaction(TransactionStatus status);

}


rollbackOnException(status,ex)方法实现:


	private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

		logger.debug("Initiating transaction rollback on application exception", ex);
		try {
		    // 使用事务管理器会滚事务
			this.transactionManager.rollback(status);
		}
		catch (TransactionSystemException ex2) {
			logger.error("Application exception overridden by rollback exception", ex);
			ex2.initApplicationException(ex);
			throw ex2;
		}
		catch (RuntimeException | Error ex2) {
			logger.error("Application exception overridden by rollback exception", ex);
			throw ex2;
		}
	}


rollbackOnException(status,ex)方法原理也是调用了管理器的rollback方法,进行事务回滚


根据事务结点创建回滚事务


在上文中我们提到SavepointManager这个接口,这个接口中三个方法,其中有一个创建事务节点和回滚到指定事务结点的方法,下面例子将演示这个两个方法的用法:


         Object savepoint=null;
            try {
                ResourceAdmin resourceAdmin = new ResourceAdmin();
                resourceAdmin.setName("张三");
                resourceAdmin.setPassword("123456");
                // 数据库保存操作
                boolean save = iResourceAdminService.save(resourceAdmin);
                if(save){
                    log.info("数据库保存1成功");
                }
                // 创建事务结点
                savepoint = status.createSavepoint();
                resourceAdmin.setName("李四");
                // 数据库保存操作
                save = iResourceAdminService.save(resourceAdmin);
                if(save){
                log.info("数据库保存2成功");
                }
                // 模拟异常
                int i=5/0;
            }catch (Exception e){
                e.printStackTrace();
                log.error("发生异常回滚事务");
                // 回滚到指定事务节点
                status.rollbackToSavepoint(savepoint);
            }


在以上代码块中,我们保存张三用户成功后,创建了一个事务节点,之后又保存了李四用户,在异常处理模块中回滚到保存李四之前,如果代码运行成功那么数据库中只会有存在张三用户,运行测试:


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务



通过上图我们可以发现事务确实是回滚到了保存李四用户之前的。


补充:其实在以上例子中我们重写doInTransactionWithoutResult方法时,如果在方法内捕获异常时我们不自己调用status.setRollbackOnly()方法回滚事务,TransactionTemplate模板方法也是会帮我们进行事务回滚和提交的。如下图:


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


我们的事务操作代码其实是放在action.doInTransaction(status);中运行的,只是把自己的数据库操作语句嵌入到该方法中,所以如果我们调用excute方法时忘记进行回滚操作,这个模板方法也是会自动帮我们进行事务的提交和回滚,所以不会造成阻塞等问题。


6.5声明式事务:


在日常开发中我相信大家用得比较多的是声明式事务,很多人包括笔者也知道其原理是使用了aop实现的,但并未真正的去缕清这个思路。接下来下我们一起从源码分析其实现方式。


事务属性接口:TransactionAttribute


public interface TransactionAttribute extends TransactionDefinition {
    //返回与此事务属性关联的限定符值这可用于选择相应的事务管理器,来处理这个特定的事务
	@Nullable
	String getQualifier();
    //这可能用于应用特定的事务行为或遵循纯粹描述性的性质。
	Collection getLabels();
	//回滚方法
	boolean rollbackOn(Throwable ex);

}


经过上面的名词解析,我相信到此大家对TransactionDefinition这个接口已经有了大概的轮廓,TransactionAttribute继承了TransactionDefinition并新增了三个抽象方法。


事务属性接口:TransactionAttributeSource


public interface TransactionAttributeSource {
    //确定给定的类是否是事务属性的候选类
	default boolean isCandidateClass(Class<?> targetClass) {
		return true;
	}
    //返回给定方法的事务属性,
	@Nullable
	TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass);
}


事务信息类:TransactionInfo


protected static final class TransactionInfo {
        // 事务管理器
	    @Nullable
		private final PlatformTransactionManager transactionManager;
		//事务属性
		@Nullable
		private final TransactionAttribute transactionAttribute;
        // 方法的全限定命名
		private final String joinpointIdentification;
        //事务状态
		@Nullable
		private TransactionStatus transactionStatus;
	}
复制代码


TransactionInfo类是 TransactionAspectSupport类中的一个内部类,由于篇幅问题,这里只是展示了TransactionInfo类的几个成员方法。


方法拦截器:


@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
	@Nullable
	Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}


事务拦截器:TransactionInterceptor


public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
    	public TransactionInterceptor(TransactionManager ptm, TransactionAttributeSource tas) {
		setTransactionManager(ptm);
		setTransactionAttributeSource(tas);
	}
    
	@Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
		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();
			}
		});
	}
}


声明式事务里面最关键的就是事务拦截器TransactionInterceptor 从以上代码中我们也可以看到其实现了方法拦截器并重写了方法拦截器中的invoke方法,


主要看invoke 方法 里面的 invokeWithTransaction()方法:


在invoke方法方法中我们主要看invokeWithinTransaction()方法:


@Nullable
	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

		// If the transaction attribute is null, the method is non-transactional.
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		//获取事务管理器
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		// 获取切入点方法的全限定命名
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}
		...
	}


以上贴出了该方法的重要代码部分,我们再看 methodIdentification(method, targetClass, txAttr); 方法的实现:


private String methodIdentification(Method method, @Nullable Class<?> targetClass,
			@Nullable TransactionAttribute txAttr) {

		String methodIdentification = methodIdentification(method, targetClass);
		if (methodIdentification == null) {
			if (txAttr instanceof DefaultTransactionAttribute) {
				methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor();
			}
			if (methodIdentification == null) {
				methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
			}
		}
		return methodIdentification;
	}


跟进ClassUtils.getQualifiedMethodName(method, targetClass);方法:


	//返回给定方法的限定名,由完全限定接口/类名+ "。"+方法名称
	public static String getQualifiedMethodName(Method method, @Nullable Class<?> clazz) {
		Assert.notNull(method, "Method must not be null");
		return (clazz != null ? clazz : method.getDeclaringClass()).getName() + '.' + method.getName();
	}


在这个方法中会返回给定方法的限定名,由完全限定接口/类名+ "。"+方法名称。


我们继续往下看源码,看下图中被红框圈起来的方法


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


createTransactionIfNecessary(tm, txAttr, joinpointIdentification)


//如果需要,根据给定的TransactionAttribute创建一个事务
	protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

		// If no name specified, apply method identification as transaction name.
		if (txAttr != null && txAttr.getName() == null) {
			txAttr = new DelegatingTransactionAttribute(txAttr) {
				@Override
				public String getName() {
					return joinpointIdentification;
				}
			};
		}

		TransactionStatus status = null;
		if (txAttr != null) {
			if (tm != null) {
				status = tm.getTransaction(txAttr);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
							"] because no transaction manager has been configured");
				}
			}
		}
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}


这里我们可以理解为createTransactionIfNecessary方法其实就是一个创建事务的方法


invocation.proceedWithInvocation():这是一个环绕通知方法,调用链中的下一个拦截器,这个方法其实就是替我们执行被@Transaction注解标记的方法


completeTransactionAfterThrowing()方法实现:


一文就够!源码级别讲解Spring事务——声明式事务和编程式事务


在上图中我们很清楚地看到两句很熟悉的代码就是 rollback() 和commit()方法


cleanupTransactionInfo()方法实现:如下图所示这个方法主要是把事务信息从ThreadLocal中清除掉


//重置TransactionInfo ThreadLocal。   
	protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
		if (txInfo != null) {
			txInfo.restoreThreadLocalStatus();
		}
	}


commitTransactionAfterReturning(txInfo)方法:


    //在调用成功完成后执行,而不是在异常被处理后执行。如果我们没有创建一个事务,就什么也不做
	protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
			}
			txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
		}
	}


在以上代码中最后一句 也是调用了事务管理器的commit()方法,一步步看了各个方法的执行后我们可以把以上代码稍微简化如下:



		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			// 1.创建事务
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				//retVal = invocation.proceedWithInvocation();
				//2.执行被事务注解标识的代码块
			}
			catch (Throwable ex) {
			    //3.发生异常回滚
				// target invocation exception
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
			    // 4.清理ThreadLocak中的事务信息
				cleanupTransactionInfo(txInfo);
			}
			// 5.提交事务
			//commitTransactionAfterReturning(txInfo);
			return retVal;
		}
 br    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {br      // Standard transaction demarcation with getTransaction and commit/rollback calls.br      // 1.创建事务br      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);brbr      Object retVal;br      try {br        // This is an around advice: Invoke the next interceptor in the chain.br        // This will normally result in a target object being invoked.br        //retVal = invocation.proceedWithInvocation();br        //2.执行被事务注解标识的代码块br      }br      catch (Throwable ex) {br          //3.发生异常回滚br        // target invocation exceptionbr        completeTransactionAfterThrowing(txInfo, ex);br        throw ex;br      }br      finally {br          // 4.清理ThreadLocak中的事务信息br        cleanupTransactionInfo(txInfo);br      }br      // 5.提交事务br      //commitTransactionAfterReturning(txInfo);br      return retVal;br    }br复制代码


通过以上代码块的简化我们知道,声明式事务也是会在代码中 创建事务,执行事务操作代码块,回滚/提交事务,是Spring框架帮我们封装了其操作方法,其本质也还是基于事务管理器的连接对象来执行事务。而声明式事务的最大特点就是将AOP和事务联系起来。


本文从源码级别简单讲解了Spring事务的支持、编程式事务和声明式事务的底层实现,这也是Spirng事务知识的冰山一角,经过本文的学习,我相信读者也能学到和加固了对于Spring事务的知识。

展开阅读全文

页面更新:2024-02-23

标签:级别   声明   事务   结点   管理器   节点   程式   抽象   源码   属性   接口   异常   操作   代码   数据库   方法   数据   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top