zoukankan      html  css  js  c++  java
  • JDBC介绍和Mybatis运行原理及事务处理

    本博客内容非自创,转载自以下三位,侵删:

    https://juejin.im/post/5ab7bd11f265da23906bfbc5

    https://my.oschina.net/fifadxj/blog/785621

    https://www.jianshu.com/p/b864aecc0de1

    JDBC相关概念

    Java程序都是通过JDBC连接数据库的,通过SQL对数据库编程,JDBC是由SUN公司提出的一些列规范,只定义了接口规范,具体实现由各个数据库厂商去实现,它是一种典型的桥接模式。ps:桥接模式是一种结构型设计模式,它的主要特点是把抽象与行为实现分离开来,分别定义接口,可以保持各部分的独立性以及应对他们的功能扩展。

    JDBC规范 

    所谓规范,就是自己定义了标准接口,做了如下抽象:用Connection代表和数据库的连接,用Statement执行SQL,用ResultSet表示SQL返回的结果,提供了对数据的便利。从Connection可以创建Statement,Statement执行查询得到ResultSet。

    上面说的Connection、Statement、ResultSet都应该是接口,具体实现由各个数据库提供商提供。有了规范,可以通过统一的接口,访问多种类型的数据库,可随便切换数据库。

    数据库驱动

    上面提到,接口的实现由各个厂商提供,那么实现类的类名就会不统一,去创建Connection对象时,代码就会写死某个实现类,切换数据库时,就需要修改代码,这样不太好。为了解决这个问题,抽象了Driver驱动的概念。

    Connection con=MySqlConnectionImpl("127.0.0.1",3306,"mi_user",userName,pwd);

    每个数据库都需要实现Driver接口,通过Driver可获得数据库连接Connection,通过反射机制动态创建。

    Class.forName("com.mysql.jdbc.Drier");

    同一个程序可能访问不同的数据库,通过DriverManager来管理驱动,Driver在初始化的时候,需要注册到DriverManager中。

    DriverManager提供了一个getConnection方法,用于建立数据库Connection:

    Connection con=DriverManager.getConnection("127.0.0.1",3306,"mi_user",userName,pwd);

    如果有多个数据库驱动,DriverManager如何区分呢,需要在数据库连接url中指定,比如mysql需要添加jdbc:mysql前缀:

    String url= "jdbc:mysql://127.0.0.1:3306/mi_user";
    Connection con=DriverManager.getConnection(url,userName,pwd)

    数据源

    数据源DataSource包含连接池和连接池管理2个部分,习惯上称为连接池。在系统初始化的时候,将数据库连接作为对象存储在内存中,当需要访问数据库时,从连接池中取出一个已建立的空闲连接对象。

    使用数据源,获取其DataSource对象,通过该对象动态的获取数据库连接。另外,DataSource对象可以注册到名字服务(JNDI)中,可以通过名字服务获得DataSource对象,无需硬性编码驱动。

    DriverManager是JDBC1提供的,DataSource是JDBC2新增的功能,提供了更好的连接数据源的方法。

    Mybatis核心组件:

    • SqlSessionFactoryBuilder:会根据配置信息或代码来生成SqlSessionFactory;
    • SqlSessionFactory:依靠工厂来生成SqlSession;
    • SqlSession:是一个既可以发送SQL去执行并返回结果,也可以获取Mapper的接口;
    • SQL Mapper:是MyBatis新设计的组件,由一个Java接口和XML文件构成,需要给出对应的SQL和映射规则。它负责发送SQL去执行,并返回结果。

    Mybatis核心流程:

    通过SqlSessionFactory获取SqlSession

    获取MapperProxy

    获取Excutor

    Mybatis事务:

    mybatis-spring的实现很大程度上依赖spring jdbc的事务管理,所以我们先看一下在spring中直接使用jdbc访问数据库时是如何处理事务的。无论你是使用@Transactional注解这样的AOP配置方式,还是TransactionTemplate这样的编码方式,最终执行的操作事务的代码都会是类似下面这样:
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    PlatformTransactionManager txManager = new DataSourceTransactionManager(dataSource);
    
    TransactionStatus status = txManager.getTransaction(def);
    try {
        //get jdbc connection...
        //execute sql...
    }
    catch (Exception e) {
        txManager.rollback(status);
        throw e;
    }
    txManager.commit(status);

    可以看到PlatformTransactionManager的getTransaction(), rollback(), commit()是spring处理事务的核心api,分别对应事务的开始,提交和回滚。

    spring事务处理的一个关键是保证在整个事务的生命周期里所有执行sql的jdbc connection和处理事务的jdbc connection始终是同一个。然后执行sql的业务代码一般都分散在程序的不同地方,如何让它们共享一个jdbc connection呢?这里spring做了一个前提假设:即一个事务的操作一定是在一个thread中执行,且一个thread中如果有多个不同jdbc connection生成的事务的话,他们必须顺序执行,不能同时存在。(这个假设在绝大多数情况下都是成立的)。基于这个假设,spring在transaction创建时,会用ThreadLocal把创建这个事务的jdbc connection绑定到当前thread,接下来在事务的整个生命周期中都会从ThreadLocal中获取同一个jdbc connection。

    我们看一下详细调用过程

    • TransactionSynchronizationManager负责从ThreadLocal中存取jdbc connection
    • 创建事务的时候会通过dataSource.getConnection()获取一个新的jdbc connection,然后绑定到ThreadLocal
    • 在业务代码中执行sql时,通过DataSourceUtils.getConnection()从ThreadLocal中获取当前事务的jdbc connection, 然后在该jdbc connection上执行sql
    • commit和rollback事务时,从ThreadLocal中获取当前事务的jdbc connection,然后对该jdbc connection进行commit和rollback

    对spring jdbc的事务处理有了了解后,我们来看mybatis是如何通过spring处理事务的。

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
    </bean>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <property name="transactionFactory">
        <bean class="org.apache.ibatis.spring.transaction.SpringManagedTransactionFactory" />
      </property> 
    </bean>
    
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
      <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>
    • mybatis-spring依赖DataSourceTransactionManager来处理事务,并没有创建自己的PlatformTransactionManager实现。
    • mybatis通过SqlSessionFactoryBuilder创建SqlSessionFactory,而mybatis-spring通过SqlSessionFactoryBean创建SqlSessionFactory。
    • 配置使用SpringManagedTransactionFactory来创建MyBatis的Transaction实现SpringManagedTransaction
    • 配置使用SqlSessionTemplate代替通过SqlSessionFactory.openSession()获取SqlSession

    然后看其调用过程

    可以看到mybatis-spring处理事务的主要流程和spring jdbc处理事务并没有什么区别,都是通过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,而且jdbc connection的创建也是通过DataSourceTransactionManager.getTransaction()完成,mybatis并没有参与其中,mybatis只是在执行sql时通过DataSourceUtils.getConnection()获得当前thread的jdbc connection,然后在其上执行sql。

    下面结合代码来看

    <SqlSessionUtils>:  
    
    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    
        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
        notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    
        SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
          return session;
        }
    
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Creating a new SqlSession");
        }
    
        session = sessionFactory.openSession(executorType);
    
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    
        return session;
      }
    
    
      private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
          PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
        SqlSessionHolder holder;
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
          Environment environment = sessionFactory.getConfiguration().getEnvironment();
    
          if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
            if (LOGGER.isDebugEnabled()) {
              LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
            }
    
            holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
            TransactionSynchronizationManager.bindResource(sessionFactory, holder);
            TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
            holder.setSynchronizedWithTransaction(true);
            holder.requested();
          } else {
            if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
              if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
              }
            } else {
              throw new TransientDataAccessResourceException(
                  "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
            }
          }
        } else {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
          }
        }
    }

    执行sql时调用sqlSessionTemplate的insert,update,delete方法,sqlSessionTemplate是DefaultSqlSession的一个代理类,它通过SqlSessionUtils.getSqlSession()试图从ThreadLocal获取当前事务所使用的SqlSession。如果是第一次获取时会调用SqlSessionFactory.openSession()创建一个SqlSession并绑定到ThreadLocal,同时还会通过TransactionSynchronizationManager注册一个SqlSessionSynchronization。

    <SqlSessionSynchronization>:
    
     public void beforeCommit(boolean readOnly) {
          // Connection commit or rollback will be handled by ConnectionSynchronization or
          // DataSourceTransactionManager.
          // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so
          // they are actually executed.
          // SpringManagedTransaction will no-op the commit over the jdbc connection
          // TODO This updates 2nd level caches but the tx may be rolledback later on! 
          if (TransactionSynchronizationManager.isActualTransactionActive()) {
            try {
              if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
              }
              this.holder.getSqlSession().commit();
            } catch (PersistenceException p) {
              if (this.holder.getPersistenceExceptionTranslator() != null) {
                DataAccessException translated = this.holder
                    .getPersistenceExceptionTranslator()
                    .translateExceptionIfPossible(p);
                if (translated != null) {
                  throw translated;
                }
              }
              throw p;
            }
          }

    SqlSessionSynchronization是一个事务生命周期的callback接口,mybatis-spring通过SqlSessionSynchronization在事务提交和回滚前分别调用DefaultSqlSession.commit()和DefaultSqlSession.rollback()

    <BaseExecutor>:
    
    public void commit(boolean required) throws SQLException {
        if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
        clearLocalCache();
        flushStatements();
        if (required) {
          transaction.commit();
        }
      }
    
      public void rollback(boolean required) throws SQLException {
        if (!closed) {
          try {
            clearLocalCache();
            flushStatements(true);
          } finally {
            if (required) {
              transaction.rollback();
            }
          }
        }
      }
    
      public void clearLocalCache() {
        if (!closed) {
          localCache.clear();
          localOutputParameterCache.clear();
        }
      }
    <SpringManagedTransaction>:
    
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);  
    
      public void commit() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
          }
          this.connection.commit();
        }
      }
    
      public void rollback() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
          }
          this.connection.rollback();
        }
      }
    <DataSourceUtils>:
    
        /**
         * Determine whether the given JDBC Connection is transactional, that is,
         * bound to the current thread by Spring's transaction facilities.
         * @param con the Connection to check
         * @param dataSource the DataSource that the Connection was obtained from
         * (may be {@code null})
         * @return whether the Connection is transactional
         */
        public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
            if (dataSource == null) {
                return false;
            }
            ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
            return (conHolder != null && connectionEquals(conHolder, con));
        }

    这里的DefaultSqlSession只会进行一些自身缓存的清理工作,并不会真正提交事务给数据库,原因是这里的DefaultSqlSession使用的Transaction实现为SpringManagedTransaction,SpringManagedTransaction在提交事务前会检查当前事务是否应该由spring控制,如果是,则不会自己提交事务,而将提交事务的任务交给spring,所以DefaultSqlSession并不会自己处理事务。

    <SpringManagedTransaction>: 
    
     public Connection getConnection() throws SQLException {
        if (this.connection == null) {
          openConnection();
        }
        return this.connection;
      }
    
      /**
       * Gets a connection from Spring transaction manager and discovers if this
       * {@code Transaction} should manage connection or let it to Spring.
       * <p>
       * It also reads autocommit setting because when using Spring Transaction MyBatis
       * thinks that autocommit is always false and will always call commit/rollback
       * so we need to no-op that calls.
       */
      private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
    
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug(
              "JDBC Connection ["
                  + this.connection
                  + "] will"
                  + (this.isConnectionTransactional ? " " : " not ")
                  + "be managed by Spring");
        }
      }

    DefaultSqlSession执行sql时,会通过SpringManagedTransaction调用DataSourceUtils.getConnection()从ThreadLocal中获取jdbc connection并在其上执行sql。

    总结:mybatis-spring处理事务的主要流程和spring jdbc处理事务并没有什么区别,都是通过DataSourceTransactionManager的getTransaction(), rollback(), commit()完成事务的生命周期管理,而且jdbc connection的创建也是通过DataSourceTransactionManager.getTransaction()完成,mybatis并没有参与其中,mybatis只是在执行sql时通过DataSourceUtils.getConnection()获得当前thread的jdbc connection,然后在其上执行sql。

    mybatis-spring做的最主要的事情是:

    1. 在SqlSession执行sql时通过用SpringManagedTransaction代替mybatis的JdbcTransaction,让SqlSession从spring的ThreadLocal中获取jdbc connection。
    2. 通过注册事务生命周期callback接口SqlSessionSynchronization,让SqlSession有机会在spring管理的事务提交或回滚时清理自己的内部缓存。


  • 相关阅读:
    应用网络电视机顶盒通过宽带网络代替数字电视
    该公众号暂时无法提供服务请稍后再试
    有点坑爹的GDALComputeRasterMinMax函数
    微信硬件平台框架说明及接入流程
    微信硬件设备接入接口协议
    微信思维
    百度SEO建议
    公众平台调整SSL安全策略,请开发者注意升级
    微信支付开发教程
    .net文件分片上传,断点续传
  • 原文地址:https://www.cnblogs.com/rayallenbj/p/9637869.html
Copyright © 2011-2022 走看看