mybatis 连接池实现

目录

配置

解析

阶段总结1:

执行

获取连接

阶段总结2:

其他参数driver、url、username、password


mybatis封装了jdbc的加载驱动和建立连接,并把连接池化以保证高效率访问数据库。

配置

一般用mybatis时,我们只需如下配置即可

<environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
      </dataSource>
    </environment>
  </environments>

其中dataSource中的type=POOLED 表示池化,其他的driver、url、username、password是加载驱动和建立连接需要的

解析

先看下解析<environments>

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      //为空从default获取
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        //获取id
        String id = child.getStringAttribute("id");
        //判断environment与id是否相等
        if (isSpecifiedEnvironment(id)) {
          //解析transactionManager获取TransactionFactory
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          //解析dataSource获取DataSourceFactory #1
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          //获取DataSource #2
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
          break;
        }
      }
    }
  }

#0处解析<transactionManager>获取TransactionFactory,这里会解析JDBC别名对应的TransactionFactory即JdbcTransactionFactory

别名在org.apache.ibatis.session.Configuration#Configuration()

typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

#1处解析<dataSource>标签获取DataSourceFactory,这里会解析POOLED别名,对应的DataSourceFactory 即PooledDataSourceFactory

别名在org.apache.ibatis.session.Configuration#Configuration()

typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);

#2处根据PooledDataSourceFactory获取DataSource 即PooledDataSource

阶段总结1:

在初始化阶段,会根据environments标签的JDBC和POOLED获取到JdbcTransactionFactory和PooledDataSource用于后续建立连接、池化操作。

执行

我们在用mybatis是一般是调用mapper的方法,类似代码如下

//获取SqlSession #1
    try (SqlSession session = sqlMapper.openSession()) {
      //获取mapper #2
      TestAuthorMapper mapper = session.getMapper(TestAuthorMapper.class);
      Author param=new Author();
      param.setId(101);
      //执行 #3
      Author author = mapper.selectAuthor(param,101);
      System.out.println(author);
    }

#1处获取SqlSession 底层是DefaultSqlSession,其包含了全局配置Configuration,执行器CachingExecutor,Jdbc事务JdbcTransaction等,代码在方法中org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

#2处从全局配置Configuration获取已经注册的mapper,并生成jdk代理,代码在

org.apache.ibatis.binding.MapperRegistry#getMapper

org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)

#3调用代理类的方法,实际执行org.apache.ibatis.binding.MapperProxy#invoke方法,最终调用org.apache.ibatis.binding.MapperMethod#execute方法,进行执行。继续跟代码,获取连接在org.apache.ibatis.executor.BaseExecutor#getConnection中。

获取连接

代码如下

protected Connection getConnection(Log statementLog) throws SQLException {
    //这里的transaction 为JdbcTransaction #1
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }
protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    //#2
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }

#1的transaction和#2处的dataSource是在openSession设置的。其中transaction为JdbcTransaction,dataSource为PooledDataSource

继续跟代码,代码比较多耐心看

public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }
private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    while (conn == null) {
      synchronized (state) {
        if (!state.idleConnections.isEmpty()) {
          // Pool has available connection #1
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          // Pool does not have available connection
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection #2
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
            // Cannot create new connection #3
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection #4
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {
                  /*
                     Just log a message for debug and continue to execute the following
                     statement like nothing happened.
                     Wrap the bad connection with a new PooledConnection, this will help
                     to not interrupt current executing thread and give current thread a
                     chance to join the next competition for another valid/good database
                     connection. At the end of this loop, bad {@link @conn} will be set as null.
                   */
                  log.debug("Bad connection. Could not roll back");
                }
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // Must wait #5
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait);
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {
          // ping to server and check the connection is valid or not
          if (conn.isValid()) {
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn);
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
              if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Could not get a good connection to the database.");
              }
              throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
            }
          }
        }
      }

    }

其中PoolState表示池化状态

#1处判断有没有空闲连接,有的话直接取出,设置池状态返回

#2处没有空闲连接,判断当前活跃的连接是否已达到最大值,没到最大值新建连接并设置池状态返回

#3处表示活跃的连接达到了最大值,会拿出最后一个活跃的连接,判断是否已经超时,如果超时了会把连接删除,回滚,重建,设置池状态返回;如果没有超时则,线程等待有执行完的连接

以上是创建连接,符合有就用没有创建的原则。执行完sql放入连接池代码如下

org.apache.ibatis.datasource.pooled.PooledDataSource#pushConnection,在调用close时执行

protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      //#1
      state.activeConnections.remove(conn);
      if (conn.isValid()) {
        //#2
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          conn.invalidate();
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          state.notifyAll();
        } else {
          //#3
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate();
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }
    }
  }

#1处调用close方法后,把当前连接在活跃列表中去除

#2处判断当前空闲连接是否达到最大值,没达到最大值,把当前真实连接封装为PooledConnection并放入连接池的空闲列表中

#3处如果空闲连接达到最大值,直接关掉连接

阶段总结2:

在配置dataSource为POOLED情况下会创建连接池PooledDataSource,其中PoolState维持池的状态,重点是idleConnections空闲连接 没有执行任务,activeConnections活跃连接 正在执行任务。poolMaximumActiveConnections池的最大活跃连接数,poolMaximumIdleConnections池的最大空闲连接数

连接池原理,当执行sql建立数据库连接时,会判断有没有空闲的连接,如果有的话直接取来用。如果没有则新建连接(分直接新建和等待新建)。执行完之后,会把连接重新构造后条件允许(空闲连接是否满)的情况下放入池中。

其他参数driver、url、username、password

org.apache.ibatis.datasource.pooled.PooledDataSource#popConnection的#2处在创建PooledConnection是又会通过dataSource.getConnection()去创建真实连接,代码如下

public Connection getConnection() throws SQLException {
    return doGetConnection(username, password);
  }
private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
      props.putAll(driverProperties);
    }
    //username
    if (username != null) {
      props.setProperty("user", username);
    }
    //password
    if (password != null) {
      props.setProperty("password", password);
    }
    return doGetConnection(props);
  }
private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }
private synchronized void initializeDriver() throws SQLException {
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }

可以看到JDBC的模板操作步骤了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackson陈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值