数据库事务的冰山一角

2年前 (2022) 程序员胖胖胖虎阿
250 0 0

1.数据库事务特性

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

  • A(Atomicity)原子性:多个会话进行数据库操作,要么同时成功,要么同时失败

  • C(Consistency)一致性:是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态

  • I(Isolation)隔离性:隔离性是当多个用户并发访问数据库时,比如同时操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离

  • D(Durability)持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

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

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

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

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

3. mysql事务隔离级别

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

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

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

数据库事务的冰山一角

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

数据库事务的冰山一角

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

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

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

数据库事务的冰山一角

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

数据库事务的冰山一角

此时我们可以看到连接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;
}
  • 常见的DataSourceTransactionManager事务管理器继承类图如下所示:

数据库事务的冰山一角


数据库事务的冰山一角

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

6.2关键名词解释:

PlatformTransactionManager:这是Spring提供的一个事务管理器的抽象接口,接口里面定义了基础的事务方法。

public interface PlatformTransactionManager extends TransactionManager {

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

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

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

}

TransactionDefinition:事务的定义,在里面定义了一些事务传播行为和事务隔离级别如:

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;
 }

}

SavepointManager:事务保存结点,当一个方法中有多个数据库修改操作,发生异常时我们并不想回滚全部操作,而只想回滚到某个节点,此时我们需要手动创建回滚的节点,SavepointManager这个接口中提供了三个方法(具体实现是DB框架)如下:

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

TransactionExecution:事务当前状态的通用表示。

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

}

TransactionStatus:事务状态表示

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

6.2 Spring编程式事务

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> 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) {
    // 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<T> {
    //当使用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);
            }

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

数据库事务的冰山一角

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

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

数据库事务的冰山一角

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

6.5声明式事务:

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

事务属性接口:TransactionAttribute

public interface TransactionAttribute extends TransactionDefinition {
    //返回与此事务属性关联的限定符值这可用于选择相应的事务管理器,来处理这个特定的事务
 @Nullable
 String getQualifier();
    //这可能用于应用特定的事务行为或遵循纯粹描述性的性质。
 Collection<String> 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();
 }

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

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

数据库事务的冰山一角

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()方法实现:

数据库事务的冰山一角

在上图中我们很清楚的看到两句很熟悉的代码就是 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;
  }

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

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


推荐阅读

1. GitHub 上有什么好玩的项目?

2. 6 月份最火的 10 个 GitHub 项目

3. 基于 Spring Boot 的百度云高仿项目

4. 盘点百度 4 个牛逼哄哄的开源项目

本文分享自微信公众号 - Java后端(web_resource)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

版权声明:程序员胖胖胖虎阿 发表于 2022年11月21日 上午8:08。
转载请注明:数据库事务的冰山一角 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...