1.PreparedStatementPool的数据结构
在PreparedStatementPool中,成员变量除了日志外,只有两项,一项为LRUCache
类型的map,另一项为DruidAbstractDataSource
的dataSource,其中map属性为已缓存语句的存储结构,dataSource为当前连接池的指向。
public class PreparedStatementPool {
private final static Log LOG = LogFactory.getLog(PreparedStatementPool.class);
private final LRUCache map;
private final DruidAbstractDataSource dataSource;
}
1.1 LRUCache
LRUCache
是继承自LinkedHashMap
来实现的LRU缓存
,其重写了LinkedHashMap
中的removeEldestEntry()
方法,该方法实现了对最近最少使用的语句进行回收操作。
public class LRUCache extends LinkedHashMap<PreparedStatementKey, PreparedStatementHolder> {
private static final long serialVersionUID = 1L;
public LRUCache(int maxSize){
//调用父类构造方法,初始化大小为druid.maxPoolPreparedStatementPerConnectionSize参数配置项,加载因子为0.75,默认按照访问顺序进行排序
super(maxSize, 0.75f, true);
}
/**
* 删除最老的一个Entry
* @param eldest 由于在构造方法中指定了访问顺序排序,所以该参数为最近最少使用的语句
*/
protected boolean removeEldestEntry(Entry<PreparedStatementKey, PreparedStatementHolder> eldest) {
//如果当前语句池中语句数量大于配置的最大数,则关闭并移除该语句
boolean remove = (size() > dataSource.getMaxPoolPreparedStatementPerConnectionSize());
if (remove) {
closeRemovedStatement(eldest.getValue());
}
return remove;
}
}
1.2 PreparedStatementKey
在判断PreparedStatementKey
是否一致时,需要所有字段一致才可以
public static class PreparedStatementKey {
/**
* 预编译的SQL语句
*/
protected final String sql;
/**
* catalog名
*/
protected final String catalog;
/**
* 方法类型枚举
*/
protected final MethodType methodType;
/**
* 返回结果集相关设置
* 可选值如下:
* ResultSet.TYPE_FORWARD_ONLY:默认的cursor 类型,仅仅支持结果集forward ,不支持backforward ,random ,last ,first 等操作。
* ResultSet.TYPE_SCROLL_INSENSITIVE:支持结果集backforward ,random ,last ,first等操作,对其它session对数据库中数据做出的更改是不敏感的。
* ResultSet.TYPE_SCROLL_SENSITIVE:支持结果集backforward,random,last ,first等操作,对其它session对数据库中数据做出的更改是敏感的,即其他session修改了数据库中的数据,会反应到本结果集中。
*/
public final int resultSetType;
/**
* 可选值如下:
* ResultSet.CONCUR_READ_ONLY:在ResultSet中的数据记录是只读的,不可以修改
* ResultSet.CONCUR_UPDATABLE:在ResultSet中的数据记录可以任意修改,然后更新到数据库,可以插入,删除,修改。
*/
public final int resultSetConcurrency;
/**
* 可选值如下:
* ResultSet.HOLD_CURSORS_OVER_COMMIT: 在事务commit或rollback后,ResultSet 仍然可用。
* ResultSet.CLOSE_CURSORS_AT_COMMIT: 在事务commit或rollback后,ResultSet 被关闭。
*/
public final int resultSetHoldability;
/**
* 自增主键设置,可选项如下:
* Statement.RETURN_GENERATED_KEYS:使用的是INSERT语句时,可以取出新插入数据行中自动增长的列的值
* Statement.NO_GENERATED_KEYS:不生成主键
*/
public final int autoGeneratedKeys;
//列映射
private final int[] columnIndexes;
private final String[] columnNames;
}
1.3 PreparedStatementHolder
以default
开头的选项为默认配置,在语句执行完被回收时,将相关参数进行重置
inUseCount
用来判断当前语句是否在被使用,如果被使用中则不允许被回收
pooling
在开启PSCache选项时,默认为true,在进行回收时会设置为false
public final class PreparedStatementHolder {
/**
* 语句键
*/
public final PreparedStatementKey key;
/**
* 实际的预编译语句对象
*/
public final PreparedStatement statement;
/**
* 命中了多少次缓存
*/
private int hitCount = 0;
/**
* fetch峰值
*/
private int fetchRowPeak = -1;
private int defaultRowPrefetch = -1;
/**
* 预读行数峰值
*/
private int rowPrefetch = -1;
/**
* Oracle隐式缓存
*/
private boolean enterOracleImplicitCache = false;
/**
* 使用中计数,当inUseCount>0时,则该语句正在执行
*/
private int inUseCount = 0;
/**
* 是否在语句池中
*/
private boolean pooling = false;
}
2.PreparedStatementPool对已缓存的语句处理
2.1 从语句池中取出语句(get方法)
/**
* 获得语句持有者对象
*/
public PreparedStatementHolder get(PreparedStatementKey key) throws SQLException {
//从LRU缓存中获取
PreparedStatementHolder holder = map.get(key);
//判空
if (holder != null) {
//如果当前语句正在使用中并且未设置sharePreparedStatements,则不返回内容
if (holder.isInUse() && (!dataSource.isSharePreparedStatements())) {
return null;
}
//增加缓存命中计数,监控指标
holder.incrementHitCount();
//增加连接池中的缓存命中计数,监控指标
dataSource.incrementCachedPreparedStatementHitCount();
//如果是Oracle数据库,将缓存状态变更到活动
if (holder.isEnterOracleImplicitCache()) {
OracleUtils.exitImplicitCacheToActive(holder.statement);
}
} else {
//增加链接池中未命中缓存数,监控指标
dataSource.incrementCachedPreparedStatementMissCount();
}
return holder;
}
2.2 置入OR归还语句(put方法)
/**
* 缓存语句
*/
public void put(PreparedStatementHolder stmtHolder) throws SQLException {
//获得实际的语句对象
PreparedStatement stmt = stmtHolder.statement;
//如果实际对象为空,不处理
if (stmt == null) {
return;
}
//如果是Oracle数据库并且使用了Oracle的隐式缓存
if (dataSource.isOracle() && dataSource.isUseOracleImplicitCache()) {
//更改当前的游标状态
OracleUtils.enterImplicitCache(stmt);
//设置Oracle隐式缓存状态
stmtHolder.setEnterOracleImplicitCache(true);
} else {
//设置未使用Oracle隐式缓存
stmtHolder.setEnterOracleImplicitCache(false);
}
//将当前语句置入语句池中
PreparedStatementHolder oldStmtHolder = map.put(stmtHolder.key, stmtHolder);
//如果之前的引用地址和现在的一致,则不继续处理内容(可以认为是一种归还)
if (oldStmtHolder == stmtHolder) {
return;
}
//如果有返回旧语句,则将旧语句回收并关闭
if (oldStmtHolder != null) {
oldStmtHolder.setPooling(false);
closeRemovedStatement(oldStmtHolder);
} else {
//如果当前语句持有者没有命中过缓存,则认为是新的一条缓存,需要增加连接池中已缓存语句数
if (stmtHolder.getHitCount() == 0) {
dataSource.incrementCachedPreparedStatementCount();
}
}
//设置当前语句在池中的状态
stmtHolder.setPooling(true);
if (LOG.isDebugEnabled()) {
String message = null;
if (stmtHolder.statement instanceof PreparedStatementProxy) {
PreparedStatementProxy stmtProxy = (PreparedStatementProxy) stmtHolder.statement;
if (stmtProxy instanceof CallableStatementProxy) {
message = "{conn-" + stmtProxy.getConnectionProxy().getId() + ", cstmt-" + stmtProxy.getId()
+ "} enter cache";
} else {
message = "{conn-" + stmtProxy.getConnectionProxy().getId() + ", pstmt-" + stmtProxy.getId()
+ "} enter cache";
}
} else {
message = "stmt enter cache";
}
LOG.debug(message);
}
}
2.3 关闭并删除语句(closeRemovedStatement)
该方法为对缓存语句的回收关闭方法,在对缓存语句弃用时会被调用
public void closeRemovedStatement(PreparedStatementHolder holder) {
if (LOG.isDebugEnabled()) {
String message = null;
if (holder.statement instanceof PreparedStatementProxy) {
PreparedStatementProxy stmtProxy = (PreparedStatementProxy) holder.statement;
if (stmtProxy instanceof CallableStatementProxy) {
message = "{conn-" + stmtProxy.getConnectionProxy().getId() + ", cstmt-" + stmtProxy.getId()
+ "} exit cache";
} else {
message = "{conn-" + stmtProxy.getConnectionProxy().getId() + ", pstmt-" + stmtProxy.getId()
+ "} exit cache";
}
} else {
message = "stmt exit cache";
}
LOG.debug(message);
}
//将语句在池中的状态设为false,软删除
holder.setPooling(false);
//如果当前语句正在使用,则暂不处理
if (holder.isInUse()) {
return;
}
//如果当前开启了Oracle的隐式缓存
if (holder.isEnterOracleImplicitCache()) {
try {
//Oracle特殊的关闭方式
OracleUtils.exitImplicitCacheToClose(holder.statement);
} catch (Exception ex) {
LOG.error("exitImplicitCacheToClose error", ex);
}
}
//调用连接池中方法进行关闭
dataSource.closePreapredStatement(holder);
}
/**
* 关闭预编译语句
*/
public void closePreapredStatement(PreparedStatementHolder stmtHolder) {
//如果当前持有者为空,不处理
if (stmtHolder == null) {
return;
}
//增加已关闭语句缓存计数,监控指标
closedPreparedStatementCountUpdater.incrementAndGet(this);
//减少已缓存语句计数,监控指标
decrementCachedPreparedStatementCount();
//增加已删除一句计数,监控指标
incrementCachedPreparedStatementDeleteCount();
//使用JDBC工具类对语句进行关闭
JdbcUtils.close(stmtHolder.statement);
}