1 Star 0 Fork 165

ElonChung / Java-Review

forked from flatfish / Java-Review 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
手写数据库连接池-Kangaroo-Pool.md 13.33 KB
一键复制 编辑 原始数据 按行查看 历史
icanci 提交于 2020-09-07 22:36 . :fire:更新文件名

手写数据库连接池 - Kangaroo-Pool

数据库连接池

什么是数据库连接池?
  • 其实是一种池化思想,为了节省资源,因为数据库连接是昂贵的资源
  • 所以就把数据放在

参考Druid数据库连接池的配置名称

  • 配置文件 注意 :配置文件必须这样写 和官方手册的一致 否则报错

  • url=jdbc:mysql:///test

  • driverClassName=com.mysql.jdbc.Driver

  • username=root

  • password=root

配置 缺省 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

数据库连接池设计思路

  • 使用集合或者队列实现(初步思路)
  • 需要的时候取,关闭的时候将其放到池子里面即可
  • 数据库连接的生产策略有多个,一个是池子创建的时候就创建连接对象
  • 另一种是需要再创建
  • 我这里实现的是需要再创建,但是还是有问题的,可以选择创建的时候不放在池子里面,当关闭的时候放在池子里面

项目目录结构

1598970611575

代码实现

  • KangarooPool
package cn.icanci.pool;

import cn.icanci.pool.exceptions.ResourceNotFoundException;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

/**
 * @Author: icanci
 * @ProjectName: kangaroo-orm
 * @PackageName: cn.icanci.pool
 * @Date: Created in 2020/9/1 16:15
 * @ClassAction: 袋鼠数据库连接池
 * -
 * - KangarooDatabaseConnectionPool
 */
public class KangarooPool {
    /*---------------------------- 单例创建对象 ---------------------------- */

    /**
     * 因为数据库连接池池是重量级对象
     * 饿汉式单例模式模式
     */
    private static final KangarooPool INSTANCE = new KangarooPool();

    private KangarooPool() {
    }

    /**
     * 返回一个连接池对象
     *
     * @return 返回KangarooPool对象
     */
    public static KangarooPool getInstance() {
        return INSTANCE;
    }

    /*---------------------------- 方法参数 ----------------------------*/
    /**
     * 执行懒加载数据库连接地址
     */
    private static String url = null;
    /**
     * 执行懒加载驱动名称
     */
    private static String driverClassName = null;
    /**
     * 执行懒加载用户名
     */
    private static String username = null;
    /**
     * 执行懒加载用户密码
     */
    private static String password = null;

    /**
     * 默认初始化连接池大小
     */
    private static Integer initialSize = 4;

    /**
     * 默认最大连接池大小为8
     */
    private static Integer maxActive = initialSize << 1;

    /**
     * 连接最大等待时间 单位:毫秒
     */
    private static Long maxWait = 10000L;

    /**
     * 当前拿到了数据库连接池的哪个值
     */
    private static Integer poolSize = 0;

    /**
     * 用来保存数据库连接
     */
    private static ConcurrentLinkedQueue<Connection> poolQueue = null;


    /**
     * 读取配置文件的对象
     */
    private static final Properties props = new Properties();

    /**
     * 文件路径地址
     */
    private static String userPropertiesPath = null;

    /*---------------------------- 静态代码块初始化 ---------------------------- */
    static {
        try {
            String resourcePath = getResourcePath();
            InputStream in = new FileInputStream(resourcePath);
            props.load(in);
            url = props.getProperty("url");
            driverClassName = props.getProperty("driverClassName");
            username = props.getProperty("username");
            password = props.getProperty("password");
            initialSize = props.getProperty("initialSize") == null ?
                    initialSize :
                    Integer.parseInt(props.getProperty("initialSize"));
            maxActive = props.getProperty("maxActive") == null ?
                    maxActive :
                    Integer.parseInt(props.getProperty("maxActive"));
            maxWait = props.getProperty("maxWait") == null ?
                    maxWait :
                    Long.parseLong(props.getProperty("maxWait"));
            poolQueue = new ConcurrentLinkedQueue<>();
            Class.forName(driverClassName);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /*---------------------------- 方法 ---------------------------- */

    /**
     * 获取配置文件路径
     *
     * @return 返回配置文件路径
     */
    private static String getResourcePath() {
        // 获取路径class编译路径
        // /E:/IdeaHome/SourceCode/kangaroo-orm/out/test/jdbc-template/
        // 此时应该删除前面的路径 /
        String pathDir = KangarooPool.class.getResource("/").getPath();
        String reallyDir = new StringBuffer(pathDir).deleteCharAt(0).toString();
        File targetPath = new File(reallyDir);
        getKangarooPoolPropertiesPath(targetPath);
        if (null != userPropertiesPath) {
            return userPropertiesPath;
        }
        throw new ResourceNotFoundException("无法找到或者加载 kangaroo-pool.properties 文件");
    }

    /**
     * 获取配置文件路径
     *
     * @param file 对象
     */
    private static void getKangarooPoolPropertiesPath(File file) {
        assert file != null;
        File[] files = file.listFiles();
        assert files != null;
        for (File f : files) {
            assert f != null;
            if (f.isDirectory()) {
                getKangarooPoolPropertiesPath(f.getAbsoluteFile());
            } else {
                if ("kangaroo-pool.properties".equalsIgnoreCase(f.getName())) {
                    userPropertiesPath = f.getAbsolutePath();
                    return;
                }
            }
        }
    }

    /**
     * 获取连接信息
     *
     * @return 返回数据库连接对象
     */
    public synchronized Connection getConnection() throws Exception {
        if (poolSize >= maxActive) {
            this.wait();
            // throw new NoConnectionException("连接池暂时没有可用的连接");
        } else {
            createConnection();
        }
        Connection remove = poolQueue.remove();
        if (poolSize > 1) {
            poolSize--;
        }
        if (null == remove) {
            createConnection();
            remove = poolQueue.remove();
        }
        return remove;
    }


    /**
     * 创建连接
     *
     * @return 返回数据库连接对象
     * @throws Exception
     */
    public synchronized void createConnection() throws Exception {
        // 每次调用,只要小于最大值,即创建对象
        if (poolSize <= maxActive) {
            Connection conn = DriverManager.getConnection(url, username, password);
            poolQueue.add(conn);
            poolSize++;
            System.out.println("创建了:" + conn);
            System.out.println("此时连接池大小:" + getPoolSize());
        } else {
            close();
        }
    }

    /**
     * 销毁连接池
     */
    public synchronized static void distory() throws SQLException {
        if (poolSize == 0) {
            return;
        }
        while (!poolQueue.isEmpty()) {
            poolQueue.remove().close();
        }
        poolSize = 0;
        System.out.println("连接池已经清空");
    }

    /**
     * 关闭数据库连接
     *
     * @param conn 需要关闭的连接
     */
    public synchronized void close(Connection conn) throws Exception {
        // 如果数据库连接为 null 直接结束方法
        if (null == conn) {
            return;
        }
        poolSize++;
        poolQueue.add(conn);
        close();
    }

    /**
     * 获取数据库连接池的连接数量
     *
     * @return 返回数据库连接池的连接数量
     */
    public Integer getPoolSize() {
        return poolSize;
    }

    /**
     * 关闭连接
     *
     * @throws Exception
     */
    private synchronized void close() throws Exception {
        // 数据连接大于 初始值,小于最大值 是可以销毁的连接
        // 等待 maxWait 的时间
        TimeUnit.MILLISECONDS.sleep(maxWait);
        if (poolSize >= initialSize) {
            poolSize--;
        }
        this.notifyAll();
    }
}
  • kangaroo-pool.properties
url=jdbc:mysql://localhost:3306/kangaroo-orm?useSSL=false&serverTimezone=UTC
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
  • NoConnectionException
package cn.icanci.pool.exceptions;

/**
 * @Author: icanci
 * @ProjectName: kangaroo-orm
 * @PackageName: cn.icanci.pool.exceptions
 * @Date: Created in 2020/9/1 18:35
 * @ClassAction: 暂时无可用连接异常
 */
public class NoConnectionException extends RuntimeException {
    private static final long serialVersionUID = 516271018338902872L;

    public NoConnectionException() {
        super();
    }

    /**
     * @param s the detail message.
     */
    public NoConnectionException(String s) {
        super(s);
    }
}
1
https://gitee.com/elonchung/Java-Review.git
git@gitee.com:elonchung/Java-Review.git
elonchung
Java-Review
Java-Review
master

搜索帮助