2022-05-13 Druid源码阅读——PreparedStatementPool的结构

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值