在实际开发中我们操作数据库获取连接的时候往往会使用连接池来减少时间消耗,在Mybatis中也支持连接池的使用。连接池就是一个保存连接的容器,该容器必须是线程安全的,不能让两个线程拿到同一个连接,同时还得实现队列的特性,即先进先出。

Mybatis对于连接池的支持比较的灵活,可支持3中方式的配置:POOLED、UNPOOLED、JNDI,接下来就详细讲解一下Mybatis中连接池的配置位置以及这3中配置方式。

1.配置位置

要想配置Mybatis对连接池的支持,我们需要在主配置文件中的dataSource标签中的type属性进行配置,该属性的取值影响着Mybatis支不支持,如何支持连接池。

<dataSource type="POOLED">
    <!--配置连接数据库的4个基本信息-->
    <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>

默认我们使用的是POOLED值,表示开启连接池。

2.POOLED

它是采用传统javax.sql.DataSource规范中的连接池,Mybatis中有对此规范的实现。与此对应的类是PooledDataSource,我们去找他的getCOnnection方法:

第一个获取链接的方法,调用了另外一个函数

public Connection getConnection() throws SQLException {
    return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
}

popConnection的实现如下:

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(this.state) {
            PoolState var10000;
          //判断空闲连接是否还有,若有直接返回一个空闲连接
            if (!this.state.idleConnections.isEmpty()) {
                conn = (PooledConnection)this.state.idleConnections.remove(0);
                if (log.isDebugEnabled()) {
                    log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                }
           //如果空闲连接没有了,并且活动连接数<最大活动连接数,那么就新创建一个连接,放入连接池
            } else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
                conn = new PooledConnection(this.dataSource.getConnection(), this);
                if (log.isDebugEnabled()) {
                    log.debug("Created connection " + conn.getRealHashCode() + ".");
                }
           //如果连接池没地方创建新的连接了,那么它就是拿出一个最老的活动连接,设置和清理后拿给我们用
            } else {
                PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
                long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
                    ++this.state.claimedOverdueConnectionCount;
                    var10000 = this.state;
                    var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                    var10000 = this.state;
                    var10000.accumulatedCheckoutTime += longestCheckoutTime;
                    this.state.activeConnections.remove(oldestActiveConnection);
                    if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                        try {
                            oldestActiveConnection.getRealConnection().rollback();
                        } catch (SQLException var16) {
                            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 {
                    try {
                        if (!countedWait) {
                            ++this.state.hadToWaitCount;
                            countedWait = true;
                        }

                        if (log.isDebugEnabled()) {
                            log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
                        }

                        long wt = System.currentTimeMillis();
                        this.state.wait((long)this.poolTimeToWait);
                        var10000 = this.state;
                        var10000.accumulatedWaitTime += System.currentTimeMillis() - wt;
                    } catch (InterruptedException var17) {
                        break;
                    }
                }
            }

            if (conn != null) {
                if (conn.isValid()) {
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }

                    conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
                    conn.setCheckoutTimestamp(System.currentTimeMillis());
                    conn.setLastUsedTimestamp(System.currentTimeMillis());
                    this.state.activeConnections.add(conn);
                    ++this.state.requestCount;
                    var10000 = this.state;
                    var10000.accumulatedRequestTime += System.currentTimeMillis() - t;
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
                    }

                    ++this.state.badConnectionCount;
                    ++localBadConnectionCount;
                    conn = null;
                    if (localBadConnectionCount > this.poolMaximumIdleConnections + this.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.");
                    }
                }
            }
        }
    }

    if (conn == null) {
        if (log.isDebugEnabled()) {
            log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        }

        throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
    } else {
        return conn;
    }
}

整体来说,Mybatis没有使用DBCP或者C3P0等第三方连接池,而是自己实现了一个连接池,在我们使用连接池获取链接的时候:

  • Mybatis首先看一下池里还有没有空闲连接,若有,则拿出空闲连接给我们。
  • 若没有,看一下现在的活动连接数有没有达到最大的限制,没有达到限制数,说明该连接池还允许创建一个新的连接,于是Mybatis就会创建一个新的连接给我们用,创建的方法就是使用Unpooled的对象。
  • 如果连接池不允许我们创建新的连接了,那么Mybatis就会从活动的连接中拿出一个最老的连接,经过处理后给我们使用。

3.UNPOOLED

它采用传统获取链接的方式,虽然也实现了javax.sql.DataSource接口,但是并没有使用池的思想,所以每次连接数据库都要重新获取一个连接。它对应的类是UnPooledDataSource,以下是此类中重要的代码片段:

第一个获取连接的函数,它啥也没干,就是调用了一下另外一个获取链接的方法:

public Connection getConnection() throws SQLException {
    return this.doGetConnection(this.username, this.password);
}

第二个获取链接的函数,该函数就是组合了一下获取链接所需要的信息,并存到了properties对象中,然后又调用了一个重载方法:

private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (this.driverProperties != null) {
        props.putAll(this.driverProperties);
    }

    if (username != null) {
        props.setProperty("user", username);
    }

    if (password != null) {
        props.setProperty("password", password);
    }

    return this.doGetConnection(props);
}

第三个获取链接的函数,该函数是标准的JDBC中获取链接的步骤,首先初始化,获取驱动,然后使用DriverManager获取链接,最后返回这个链接,可看到,该过程没有用到池的思想

private Connection doGetConnection(Properties properties) throws SQLException {
    this.initializeDriver();
    Connection connection = DriverManager.getConnection(this.url, properties);
    this.configureConnection(connection);
    return connection;
}

4.JNDI

采用服务器提供的JNDI技术来获取DataSource对象,不同的服务器所拿到的DataSource是不一样的。如果不是Maven的war工程,是不能使用的。

补充

在Mac下的IDEA中,可以使用Command+O的快捷键,按类名查找类文件。