JavaJava 程序员

关于数据库连接池的一些总结

2022-05-28  本文已影响0人  程序花生

啥是连接池

数据库连接池是在程序初始化时创建一定数量的数据库连接对象并将其保存在一块内存区中,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个;释放空闲时间超过最大空闲时间的数据库连接以避免因为没有释放数据库连接而引起的数据库连接遗漏。

为啥要使用连接池呢

其实在业务量流量不大,并发量也不大的情况下,比如我们自己写实验,连接临时建立完全可以。 但是在企业里面,我们并发量很大,达到百级、千级,其中建立连接、关闭连接的操作会造成性能瓶颈,所以得考虑连接池来优化 操作:

  1. 取出连接(业务服务启动时,初始化若干个连接,放在连接存储中)
  2. 发送请求(当有请求,从连接存储中中取出)
  3. 放回连接(执行完毕,连接放回连接存储中)

这里对连接存储的数据结构,并维护连接,就是连接池。

池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

关于commons-dbutils

在经历过三层架构的学习后,我们知道对于增删改的通用方法 execute(String sql ,Object[] os) 来说,只要传入 sql 参数和置换参数 os ,就能实现相应的增删改功能;但对于查询方法 query(String sql, Object[] os) 而言,为了能够通用,只能封装到结果集 ResultSet ,而不能继续封装成对象或集合等类型。

要解决【无论是增删改还是都可以得到彻底封装】的问题,我们就得使用 commons-dbutils 类库来实现。

commons-dbutilsApache 组织提供的一个 JDBC 工具类库,极大地简化了 JDBC 的代码量,并且不会影响程序的性能。

commons-dbutils 类库主要包含了两个类和一个接口,如下表:

全 名 类 或 接 口
org.apache.commons.dbutils.DbUtils class
org.apache.commons.dbutils.QueryRunner class
org.apache.commons.dbutils.ResultSetHandler intereface

DbUtils 类

DbUtils 是一个工具类,提供了关闭连接、事务提交/回滚、注册 JDBC 驱动程序等常用方法。DbUtils 类中的方法都是 public static 修饰的(除了构造方法),常用方法如下图所示。

QueryRunner 类

QueryRunner 类主要用于执行增删改查等 SQL 语句。特别的,如果执行的是查询 SQL,还需要结合 ResultSetHandler 接口来处理结果集。QueryRunner 类的常用方法如下图所示。

ResultSetHandler 接口及其实现类

ResultSetHandler 接口用于处理 ResultSet 结果集,它可以将结果集中的数据封装成单个对象、数组、List、Map 等不同形式。

ResultSetHandler 接口有很多不同的实现类,如下图所示。

这里我们介绍一下事务

在学习事务管理之前,我们有必要了解一下 ThreadLocal<T> 类。

ThreadLocal 可以为变量在每个线程中都创建一个副本,每个线程都可以访问自己内部的副本变量。因此,ThreadLocal 被称为线程本地变量(或线程本地存储)。

观察下面的代码,思考哪些地方可能会引发的线程安全问题?

public class ConnectionManager {
    private static Connection conn = null;
    public static Connection getConnection() throws /*…*/    {
        if(conn == null)    {
               conn = DriverManager.getConnection(/*...*/);
           }
           return conn;
    }

    public static void closeConnection() throws /*…*/{
        if(conn!=null)
            conn.close();
    }
}

多线程场景下引发的问题分析:

  1. 因为 conn 是静态全局变量(用于共享),那么就有可能在一个线程使用 conn 操作数据库时,另外一个线程也同时调用 closeConnection() 关闭连接。
  2. 如果多个线程同时进入 getConnection() 方法的 if 语句,那么就会多次创建 conn 对象。

关于上述问题,你可能会想到使用“线程同步”来解决:将 conn 变量、getConnection()closeConnection() 使用 synchronized 进行同步处理。以上面的代码为例,“线程同步”虽然可以解决问题,但会造成极大的性能影响:因为使用了线程同步后,当一个线程正在使用 conn 访问数据库时,其它线程只能等待。

以上面代码为例,引发线程安全问题的实质是因为 conn 变量、getConnection() 和 closeConnection() 都是共享的 static 变量(或方法)而造成的。实际上,一个线程只需要维护自己的 conn 变量,而不需要关心其它线程是否对各自的 conn 进行了修改。因此,不用 static 修饰也是可以的,如下:

public class ConnectionManager {
    // 没有 static 修饰
    private Connection conn = null;
    // 没有 static 修饰
    public Connection getConnection() throws SQLException {
        if (conn == null) {
            conn = DriverManager.getConnection(/*...*/);
        }
        return conn;
    }
    // 没有 static 修饰
    public void closeConnection() throws SQLException {
        if (conn != null)
            conn.close();
    }
}
class Dao{
       public void insert() throws SQLException {
           // 将 connectionManager 和 conn 定义为局部变量
        ConnectionManager connectionManager = new ConnectionManager();
        Connection conn = connectionManager.getConnection();
        // 使用 conn 访问数据库...
        connectionManager.closeConnection();
    }
}

以上代码,将 conn 及相关方法的 static 修饰符去掉,然后在每个使用 conn 的方法中(如 insert())都创建局部变量 conn。这样一来,因为每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是,由于在方法中的 conn 变量会在方法结束时自动释放空间,因此频繁的方法调用就会频繁地开启和关闭数据库连接,从而严重影响程序执行性能。

ThreadLocal<T> 可以在每个线程中对该变量创建一个副本,即每个线程内部都会有一个该变量的副本,该副本在线程内部任何地方都可以共享使用,但不同线程的副本之间互不影响。

ThreadLocal<T> 类中有以下几个常用方法,如表所示:

方 法 简 介
public T get() 获取 ThreadLocal 在当前线程中保存的变量副本。
public void set(T value) 设置当前线程中变量的副本。
public void remove() 移除当前线程中变量的副本。
protected T initialValue() 延迟加载的方法,一般在使用时重写该方法。

连接池和 DbUtils 这个工具类主要有以下重点:

写在最后

其实本文对连接池的理解还是比较片面,希望大家在看了以后还是好好深入理解一下。

作者:My2538772270
链接:https://juejin.cn/post/7102609261204602893
来源:稀土掘金

上一篇下一篇

猜你喜欢

热点阅读