今天我们来看下PS Cache,即poolPreparedStatements
参数,当设置为true
会开启缓存,我们一般执行一个语句之后会直接将PreparedStatements
关闭,但是当一个语句多次调用但参数不一样时,加入缓存能极大的提升性能。
可以通过设置poolPreparedStatements
为true
开启,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
。
获取是在DruidPooledConnection
的prepareStatement
方法里,当开启了缓存时会拿到包装的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
是缓存最底层的实现,其实就是一个LRU Cache
,用的java
的LinkedHashMap
,因为实际上LinkedHashMap
其实就是一个LRU Cache
,底层实现是double list
+Map
。LRU 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
有一些特殊优化貌似,这块就不细究了。