Druid源码阅读系列(八)

今天我们来看下PS Cache,即poolPreparedStatements参数,当设置为true会开启缓存,我们一般执行一个语句之后会直接将PreparedStatements关闭,但是当一个语句多次调用但参数不一样时,加入缓存能极大的提升性能。

开启位置

可以通过设置poolPreparedStatementstrue开启,mysql数据库建议关闭。也可以设置maxPoolPreparedStatementPerConnectionSize>0使其开启。但反之不行,只设置poolPreparedStatements为true但不设置maxPoolPreparedStatementPerConnectionSize>0时poolPreparedStatements会被默认为N。参考下面代码

public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) {
    if (maxPoolPreparedStatementPerConnectionSize > 0) {
        this.poolPreparedStatements = true;
    } else {
        this.poolPreparedStatements = false;
    }

    this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
}
缓存放入

DruidPooledPreparedStatement调用close方法时会调用conn.closePoolableStatement(this)方法,DruidPooledPreparedStatement构造函数里会将pooled = conn.getConnectionHolder().isPoolPreparedStatements();成员变量的pool设置为开启时设定的poolPreparedStatements值。

public void closePoolableStatement(DruidPooledPreparedStatement stmt) throws SQLException {
    PreparedStatement rawStatement = stmt.getRawPreparedStatement();

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

    PreparedStatementHolder stmtHolder = stmt.getPreparedStatementHolder();
    stmtHolder.decrementInUseCount();
    if (stmt.isPooled() && holder.isPoolPreparedStatements() && stmt.exceptionCount == 0) {
        holder.getStatementPool().put(stmtHolder);

        stmt.clearResultSet();
        holder.removeTrace(stmt);

        stmtHolder.setFetchRowPeak(stmt.getFetchRowPeak());

        stmt.setClosed(true); // soft set close
    } else if (stmt.isPooled() && holder.isPoolPreparedStatements()) {
        // the PreparedStatement threw an exception
        stmt.clearResultSet();
        holder.removeTrace(stmt);

        holder.getStatementPool()
                .remove(stmtHolder);
    } else {
        try {
            //Connection behind the statement may be in invalid state, which will throw a SQLException.
            //In this case, the exception is desired to be properly handled to remove the unusable connection from the pool.
            stmt.closeInternal();
        } catch (SQLException ex) {
            this.handleException(ex, null);
            throw ex;
        } finally {
            holder.getDataSource().incrementClosedPreparedStatementCount();
        }
    }
}

从上面代码可以看出如果开启了PS Cache会直接将stmtHolder放进PreparedStatementPool里面进行缓存,会将相应的resultSet清除掉,但是不会真实关闭stmt,而仅仅是将close标记改为了true

缓存获取

获取是在DruidPooledConnectionprepareStatement方法里,当开启了缓存时会拿到包装的stmtHolder对象。最后得到的DruidPooledPreparedStatement对象其实就是缓存的PreparedStatement对象。

public PreparedStatement prepareStatement(String sql) throws SQLException {
    checkState();

    PreparedStatementHolder stmtHolder = null;
    PreparedStatementKey key = new PreparedStatementKey(sql, getCatalog(), MethodType.M1);

    boolean poolPreparedStatements = holder.isPoolPreparedStatements();

    if (poolPreparedStatements) {
        stmtHolder = holder.getStatementPool().get(key);
    }

    if (stmtHolder == null) {
        try {
            stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql));
            holder.getDataSource().incrementPreparedStatementCount();
        } catch (SQLException ex) {
            handleException(ex, sql);
        }
    }

    initStatement(stmtHolder);

    DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement(this, stmtHolder);

    holder.addTrace(rtnVal);

    return rtnVal;
}
PreparedStatementPool

PreparedStatementPool是缓存最底层的实现,其实就是一个LRU Cache,用的javaLinkedHashMap,因为实际上LinkedHashMap其实就是一个LRU Cache,底层实现是double list+MapLRU Cache具体实现在LeetCode有现成的题,有兴趣的朋友可以研究下,很有意思。

构造的时候会根据设置的MaxPoolPreparedStatementPerConnectionSize限定整个LRU Cache的大小,如果没有设置则默认为16。

public PreparedStatementPool(DruidConnectionHolder holder){
    this.dataSource = holder.getDataSource();
    int initCapacity = holder.getDataSource().getMaxPoolPreparedStatementPerConnectionSize();
    if (initCapacity <= 0) {
        initCapacity = 16;
    }
    map = new LRUCache(initCapacity);
}

get()put()方法除了从map里面拿值和设置值之外还有一些额外的计数操作,put()代码里有对oracle有一些特殊优化貌似,这块就不细究了。

你可能感兴趣的:(Druid,源码阅读,缓存,数据库,mysql)