在学会了commons-pool2的通用模型后,我又尝试了一下 HTTP的性能,但却没有任何效果。在我自己的现场试验中,它的表现非常出色。我事后一琢磨,发现自己用得不对。原本我还打算自己编写 Redis的链接,但是 Jedis的链接竟然是commons-pool2的,这让我有些惊讶,似乎也是这么想的。commons-pool2用于创建一个链接池很好。

我仔细搜索了一下,发现缺少一个本地 MySQL连接池,而非像 springboot一样,必须先启动一项服务。当然,我也有,但是我很想自己做一份,做一些测试,所以就没有去看。

可池化对象

首先,我们需要一个可池化的对象,我选择了我自己编写的 MySQL对象com.funtester.db.my SQL。我打算把它当作一个基本的可池化对象。

package com.funtester.db.mysql;

import com.funtester.base.interfaces.IMySqlBasic;
import com.funtester.config.SqlConstant;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * mysql操作的基础类
 * <p>用于存储数据,多用于爬虫</p>
 */

public class FunMySql extends SqlBase implements IMySqlBasic {

    /**
     *  {@link SqlConstant#FUN_SQL_URL}会替换IP到URL
     */

    String url;

    /**
     * 库
     */

    String database;

    /**
     * 用户
     */

    String user;

    /**
     * 密码
     */

    String password;

    Connection connection;

    Statement statement;

    /**
     * 私有构造方法
     *
     * @param url      连接地址,包括端口
     * @param database 库
     * @param user     用户名
     * @param password 密码
     */

    public FunMySql(String url, String database, String user, String password) {
        this.url = url;
        this.database = database;
        this.user = user;
        this.password = password;
        getConnection(database);
    }

    /**
     * 初始化连接
     */

    @Override
    public void getConnection() {
        getConnection(EMPTY);
    }

    /**
     * 执行sql语句,非query语句,并不关闭连接
     *
     * @param sql
     */

    @Override
    public void executeUpdateSql(String sql) {
        SqlBase.executeUpdateSql(connection, statement, sql);
    }

    /**
     * 查询功能
     *
     * @param sql
     * @return
     */

    @Override
    public ResultSet executeQuerySql(String sql) {
        return SqlBase.executeQuerySql(connection, statement, sql);
    }

    /**
     * 关闭query连接
     */

    @Override
    public void over() {
        SqlBase.close(connection, statement);
    }

    @Override
    public void getConnection(String database) {
        if (connection == null)
            connection = SqlBase.getConnection(SqlConstant.FUN_SQL_URL.replace("ip", url).replace("database", database), user, password);
        if (statement == null) statement = SqlBase.getStatement(connection);
    }

}

池化工厂

相对连接,创建com.funtester.db.mysql.FunMySql的时候,顺便一起初始化MySQL连接。然后再com.funtester.db.mysql.MysqlPool.FunTester#destroyObject的时候进行连接的回收。

    /**
     * 池化工厂类
     */

    private class FunTester extends BasePooledObjectFactory<FunMySql> {

        @Override
        FunMySql create() throws Exception {
            return new FunMySql(url, database, user, password)
        }

        @Override
        PooledObject<FunMySql> wrap(FunMySql obj) {
            return new DefaultPooledObject<FunMySql>(obj)
        }

        @Override
        void destroyObject(PooledObject<FunMySql> p) throws Exception {
            p.getObject().over()
            super.destroyObject(p)
        }
    }

对象池

这里显得有些冗余,后面再使用过程中,我会继续优化。通过创建一个com.funtester.db.mysql.MysqlPool对象,获取一个com.funtester.db.mysql.FunMySql对象池。

/**
 * 自定义MySQL连接池对象
 */

class MysqlPool extends PoolConstant {

    private static final Logger logger = LogManager.getLogger(MysqlPool.class);

    /**
     * {@link com.funtester.config.SqlConstant#FUN_SQL_URL}会替换IP到URL*/

    String url;

    /**
     * 库
     **/

    String database;

    /**
     * 用户
     **/

    String user;

    /**
     * 密码
     **/

    String password;

    private GenericObjectPool<FunMySql> pool

    MysqlPool(String url, String database, String user, String password) {
        this.url = url
        this.database = database
        this.user = user
        this.password = password
        init()
    }

    /**
     * 初始化连接池
     * @return
     */

    def init() {
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(MAX);
        poolConfig.setMinIdle(MIN_IDLE);
        poolConfig.setMaxIdle(MAX_IDLE);
        poolConfig.setMaxWaitMillis(MAX_WAIT_TIME);
        poolConfig.setMinEvictableIdleTimeMillis(MAX_IDLE_TIME);
        pool = new GenericObjectPool<FunMySql>(new FunTester(), poolConfig);
    }
}

API封装

自从学习了Go语言的gorm框架和Redis框架,我发现其实不用把池化相关信息不用暴露出来,直接封装原始的API,暴露给用户使用,这样用户就不用关心连接的回收问题了。


    /**
     * 借出对象
     * @return
     */

    def borrow() {
        try {
            return pool.borrowObject()
        } catch (e) {
            logger.warn("获取${JSONObject.class} 失败", e)
        } finally {
            new JSONObject()
        }
    }

    /**
     * 归还对象
     * @param funMySql
     * @return
     */

    def back(FunMySql funMySql) {
        pool.returnObject(funMySql)
    }

    /**
     * 执行update SQL
     * @param sql
     * @return
     */

    def execute(def sql) {
        def driver = borrow()
        try {
            driver.executeUpdateSql(sql)
        } catch (e) {
            logger.warn("执行:{}失败", sql)
        } finally {
            back(driver)
        }
    }

    /**
     * 执行查询SQL
     * @param sql
     * @return
     */

    def query(def sql) {
        def driver = borrow()
        try {
            return driver.executeQuerySql(sql)
        } catch (e) {
            logger.warn("执行:{}失败", sql)
        } finally {
            back(driver)
        }
    }