如何手写一个简易的数据库连接池

前言

要手写实现一个数据库连接池首先肯定要理解其作用和工作原理

数据库连接池的作用包括:
1 节省资源:数据库连接池会对连接进行复用,避免了每次连接时都需要建立新的连接和释放连接的开销,从而节省了资源。
2 提高性能:数据库连接池可以预先初始化一定数量的连接,当请求到来时,可以直接从连接池中取出连接,避免了连接初始化和释放的时间,从而提高了系统的响应速度,更有效的应对大量请求和并发。
3 避免连接泄露:连接池会调控连接的使用情况,防止因为应用程序没有正确释放连接而造成连接泄露的问题,并定期清理连接。
4 方便管理:连接池提供了一系列的管理工具,如连接池大小的动态调整、连接的最大使用时间的限制等,方便对连接池进行管理和维护。(动态扩缩没写)

数据库连接池的大致工作原理:
当应用程序需要连接数据库时,它会先向连接池请求连接,连接池会检查连接是否可用,如果有可用的连接,则返回给应用程序使用;如果没有可用连接,则连接池会创建新的连接,直到连接池中连接数量达到上限。当应用程序不再需要连接时,它会将连接返回给连接池,连接池将连接标记为可用状态,以便下一次请求时可以直接使用。同时,连接池还会对连接进行管理,包括连接的创建、销毁、监控和维护等,以保证连接池的可靠性和稳定性。

接着是手写连接池代码的解析:
首先编写配置类 不做解释

public class DbConfig {
    private static String driver; //连接驱动
    private static String url; //连接地址
    private static String username; //连接名
    private static String password; //连接密码
    private static Integer minFreeConnections=2; //空闲连接池,最小连接数,默认为2
    private static Integer maxFreeConnections=8; //空闲连接池,最大连接数,默认为8
    private static Integer maxActiveConnection=8; //活跃连接池,最大连接数,默认为8
    private static Integer initConnections=2; //初始化连接数,默认为2个
    private static Long connectionTimeOut=1000*60*20L; //连接超时时间,默认为20分钟
    private static Long recheckTime=1000*60L; //自检循环时间,默认为60秒
    


    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("D:\\javaProject\\TVWebNew2\\src\\main\\resources\\jdbc.properties"));
            url = properties.getProperty("jdbc.url");
            driver = properties.getProperty("jdbc.driver");
            username = properties.getProperty("jdbc.username");
            password = properties.getProperty("jdbc.password");
            minFreeConnections = Integer.parseInt(properties.getProperty("jdbc.minFreeConnections"));
            maxFreeConnections = Integer.parseInt(properties.getProperty("jdbc.maxFreeConnections"));
            maxActiveConnection = Integer.parseInt(properties.getProperty("jdbc.maxActiveConnection"));
            initConnections = Integer.parseInt(properties.getProperty("jdbc.initConnections"));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public DbConfig() {
    }


// 省略.......get/set方法.......

接着创建ConntectionBean类用于维护其相应的连接和该连接开始创建使用的时间,用于后面判断是否超时

public class ConnectionBean {
    private Connection conn;
    private Long startTime;

    public ConnectionBean(Connection conn, Long startTime) {
        this.conn = conn;
        this.startTime = startTime;
    }

    public Connection getConn() {
        return conn;
    }

    public void setConn(Connection conn) {
        this.conn = conn;
    }

    public Long getStartTime() {
        return startTime;
    }

    public void setStartTime(Long startTime) {
        this.startTime = startTime;
    }
}

最后开始编写核心Pool
连接池的核心就是两个集合,分别维护空闲连接和活跃连接
创建成员

//空闲连接池  使用线程安全的集合
private CopyOnWriteArrayList<Connection> freePool = new CopyOnWriteArrayList<Connection>();

//活跃连接池  使用线程安全的集合
private CopyOnWriteArrayList<ConnectionBean> activePool = new CopyOnWriteArrayList<ConnectionBean>();

//连接池配置
private DbConfig dbConfig;

//记录已创建的连接数
private AtomicInteger countConnection = new AtomicInteger(0);

构造器 用于初始化配置文件 和创建连接 并开启自检线程

public ConnectionPool(DbConfig dbConfig) {
    this.dbConfig = dbConfig;
try {
    Class.forName(dbConfig.getDriver());

    //初始化连接
    for (int i = 0; i < dbConfig.getInitConnections(); i++) {
        //创建连接
        Connection connection = newConnection();
        if (connection != null) {
            freePool.add(connection);
        }
    }

	//开启一个固定时间间隔启动方法的线程
    TimeCheck timeCheck = new TimeCheck();
    Timer timer = new Timer();
    timer.schedule(timeCheck,0,dbConfig.getRecheckTime());


} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

}

创建新连接并放入空闲连接池 不解释

//创建新连接
private synchronized Connection newConnection() {
    Connection connection = null;
    try {
        connection = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
        //连接数加1
        countConnection.incrementAndGet();
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
    return connection;
}

公共方法获取连接
代码逻辑在注释中 不难理解
这里阻塞线程时使用的是固定时间的等待,这样会导致有可能一直等待直到超时
但我尚未找到较好的方法优化

//从线程池中获取连接
public synchronized Connection getConnection() {
    Connection connection = null;
    try {
        // 如果当前线程池中还有可用连接,直接从连接池中获取
        if (activePool.size() < dbConfig.getMaxActiveConnection()) {
            //如果空闲连接池里存在连接,拿出连接
            if (freePool.size() > 0) {
                connection = freePool.remove(0);
            } else {
                //不够就创建连接
                connection = newConnection();
            }
            //对连接进行校验
            if (isAlive(connection)) {
                //通过就放入活跃连接池
                activePool.add(new ConnectionBean(connection, System.currentTimeMillis()));
            }
        }else {
            //如果没有就等待
            wait(dbConfig.getRecheckTime());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return connection;
}

释放连接

public synchronized boolean releaseConnection(Connection connection) {
    //判断是否可用
    if (isAlive(connection)) {
        //3.从活跃池中移除连接
        for (int i = 0; i < activePool.size(); i++) {
            if (activePool.get(i).getConn() == connection) {
                activePool.remove(i);//可能出现下标问题
            }
        }
        //2.判断空闲池是否已满
        if (freePool.size() < dbConfig.getMaxFreeConnections()) {
            //未满,回收连接
            freePool.add(connection);


        } else {
            //已满
            try {
                countConnection.decrementAndGet();
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        //4.唤醒所有被阻塞的线程
        notifyAll();
        return true;
    }
    return false;
}

接下来就是重要的自检 包括:
1. 检查所有空闲连接是否可用,不可用的直接关闭连接
2. 检查当前连接数是否满足最低空闲连接数,若低于最小空闲数,新增连接放入空闲池 维护空闲连接池
3. 检查活跃连接池中的连接是否超时 实现连接的最大使用时间和连接的定期清理

class TimeCheck extends TimerTask{
    @Override
    public void run() {
        // 1. 检查所有空闲连接是否可用,不可用的直接关闭连接
        Iterator<Connection> iterator = freePool.iterator();
        while (iterator.hasNext()) {
            Connection connection = iterator.next();
            if (!isAlive(connection)) {
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
                //不可用直接关闭移除,连接数减1
                iterator.remove();
                countConnection.decrementAndGet();
            }
        }


        // 2. 检查当前连接数是否满足最低空闲连接数,若低于最小空闲数,新增连接放入空闲池 维护空闲连接池
        int count = countConnection.get();
        if (count < dbConfig.getMinFreeConnections()) {
            //循环两者之差,也就是所需要新建的连接
            for (int i = count; i < dbConfig.getMinFreeConnections(); i++) {
                Connection connection = newConnection();
                //非空就放入空闲连接池
                if (connection != null) {
                    freePool.add(connection);
                }
            }
        }
        //            System.out.println("连接检查...");

        // 3 检查活跃连接池中的连接是否超时  实现连接的最大使用时间和连接的定期清理
        for (int i = 0; i < activePool.size(); i++) {
            ConnectionBean connectionBean = activePool.get(i);
            //获取该连接开始使用连接
            long startTime = connectionBean.getStartTime();
            //获取系统当前时间
            long currentTimeMillis = System.currentTimeMillis();
            try {
                //如果超时
                if ((currentTimeMillis - startTime) > dbConfig.getConnectionTimeOut()) {
                    Connection conn = connectionBean.getConn();
                    if (isAlive(conn)) {
                        //关闭
                        conn.close();
                        //移除活跃连接池
                        freePool.remove(i);
                        countConnection.decrementAndGet();
                        System.out.println("关闭超时连接...");

                    }
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

这里使用到了成员内部类,因为自检方法需要定时开启线程,一个类继承TimerTask就可以调用run方法,并设置调用时间。相关的代码逻辑都详细写在代码块中,不做过多解释

你可能感兴趣的:(java,sql)