Mybatis + Druid 数据库连接池的连接缓存原理

Mybatis + Druid 数据库连接池的连接缓存原理

Mybatis 默认数据库连接池缓存原理

Mybatis 默认数据库连接池缓存原理,和为什么要使用连接池, 网站上较多文章可以通过这个链接查看https://www.cnblogs.com/yougewe/articles/10061276.html

Mybatis+Druid 连接池原理

在Druid中定义了DruidDataSource 类来维护数据库连接池状态,定义了connections数组来保存已经建立的数据库连接。

    private volatile DruidConnectionHolder[] connections;

driud 将与数据库的物理连接(最原始的数据库Connection)保存在DruidConnectionHolder中其中,DruidConnectionHolder保存了数据库连接Connection和连接监控信息。


Druid 连接池初始化和连接获取

Mybatis+Druid 数据库连接池一起使用时,首先是通过SqlSessionFactory.openSession获取一个SqlSession的实例,执行数据库操作时又通过SqlSession获取对应的Mapper,调用Mapper的方法,实际数据库连接Connection的创建是在执行数据库操作的时候建立的。获取数据库连接的逻辑是在DruidDataSource中

    public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
        //按配置初始化数据库连接池
        init();

        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis); //从数据库连接池中获取数据库连接
        }
    }

在调用DruidDataSource.geConnection时首先会调用init方法,init方法按照mybatis的配置进行数据库连接池的初始化,初始化之后getConnectionDirect再从连接池中返回数据库连接。

其中init方法中创建数据库连接池的部分逻辑如下:


//..校验配置信息合法性,通过配置信息创建加载驱动类

                while (poolingCount < initialSize) {
                    try {
                        PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection(); //通过读取的配置创建于数据库的连接
                        DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo); //用DruidConnectionHolder保存的连接和连接信息
                        connections[poolingCount++] = holder;  //将DruidConnectionHolder保存到连接池中
                    } catch (SQLException ex) {
                        LOG.error("init datasource error, url: " + this.getUrl(), ex);
                        if (initExceptionThrow) {
                            connectError = ex;
                            break;
                        } else {
                            Thread.sleep(3000);
                        }
                    }
                }

init() 方法中按配置的初始池连接数初始化数据库连接,前后部分还的校验配置的和合法性、统计活跃的连接和僵尸连接、设置连接状态回调处理器等操作,这写内容不展示。

然后下一步getConnectionDirect方法就是直接从连接池中获取上一步初始化的数据库连接,关键逻辑如下:


    if (maxWait > 0) {
        holder = pollLast(nanos);//从connections数组中获取数据库连接
    } else {
        holder = takeLast();
    }

    if (holder != null) {
        activeCount++;
        if (activeCount > activePeak) {
            activePeak = activeCount;
            activePeakTime = System.currentTimeMillis();
        }
    }

getConnectionDirect 的主要工作就是通过pollLast方法从connections数组中获取数据库连接,并返回,实现从连接池中获取数据库连接的效果。

Druid 连接回收重用

在Mybatis中SqlSession在非线程安全的,不能被共享的, 所以在每一次业务调用触发并返回响应之后,就应该调用sqlSession的close()方法关闭它。如果不使用数据库连接池调用sqlSession.close()方法就会连通对应的数据库连接一同关闭掉。

SqlSession.close的调用链


//1.  DefaultSqlSession.java
 @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }

//2.  CachingExecutor.java
  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }

  //3. JdbcTransaction.java
  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close(); //关闭数据库连接
    }
  }

如代码所示最后调用connection.close()了,在没有数据库连接池的情况下与数据库的连接connection就是关闭了,但是在使用Druid数据库连接池时connection的是com.alibaba.druid.pool.DruidPooledConnection 类的实例,DruidPooledConnection类实现了java.sql.Connection接口,并重写了close()方法,在重写的close方法中并没有直接关闭掉数据库连接,而是将数据库连接回收到了DruidDataSource中的connections数组中。回收方法是DruidDataSource的recycle方法中,处理逻辑如下:

    lock.lock();
    try {
        activeCount--;
        closeCount++;

        result = putLast(holder, currentTimeMillis);//调用putLast方法将DruidConnectionHolder回收到connections中
        recycleCount++;
    } finally {
        lock.unlock();
    }

putLast逻辑如下:


    boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
        if (poolingCount >= maxActive) {
            return false;
        }

        e.lastActiveTimeMillis = lastActiveTimeMillis;
        connections[poolingCount] = e;//将当前关闭sqlSession对应的数据库连接DruidConnectionHolder放回connections中
        incrementPoolingCount();

        if (poolingCount > poolingPeak) {
            poolingPeak = poolingCount;
            poolingPeakTime = lastActiveTimeMillis;
        }

        notEmpty.signal();
        notEmptySignalCount++;

        return true;
    }

就是这样的流程在使用Mybatis+Druid的时候关闭SqlSession,对应的连数据库连接不会直接关掉,而是重新保存到DruidDataSource.connections中,在下一次执行数据库操作时可以直接从DruidDataSource.connections中直接获取已建立的连接,从而优化创建数据库连接带来的效率影响。

上一篇:C++/QT设计模式-观察者模式,C++11改进


下一篇:MySQL查看数据库连接数