• 数据库中的数据源和数据库连接池的关系
  • 发布于 2个月前
  • 310 热度
    0 评论
前言
我学习的过程中,对于连接池和数据源分得不是很清楚,而且我发现有的人将数据库等同于数据源,或者将数据源等同于连接池,实际上这些说法并不准确。在某次工作中,同事 A 说道,这个数据源不行,那么换一个数据源就可以了,结果我看他操作,原来是改写了配置中的数据库连接的 URL,当时我在想,这就是换数据源了?我以为说是把 Druid 这个数据源换掉。至于为什么会这么想,主要是因为有个 DruidDataSource。

现在,搞清楚它们的区别不妨听我说说,欢迎大家在评论区说出你的看法!

数据库
一提到数据库,大家都会想到 MySQL、Oracle、PostgreSQL 这些。我们也习惯这样讲:我这个项目的数据库使用的是 MySQL,是吧。实际上,严格来讲,这些是数据库管理系统(Database Management System,DBMS),它们是一种可以操作和管理数据库(Database)的软件。真正的数据库是指存储数据的仓库,这些数据都是持久化存储在计算机的硬盘上的。

比如 MySQL,我们在 MySQL 客户端使用 CREATE DATABASE db_demo; 命令,这样就创建了一个名为 db_demo 的数据库。我们可以使用 SHOW VARIABLES LIKE '%datadir'; 命令查看数据库存放在哪个地方。

数据库连接池
那什么是数据库连接池呢?在说什么是连接池之前,我们先说说什么是连接(Connection、Connect)。

连接
在一开始学习 MySQL 的时候,我们通过 MySQL 的客户端来连接上 MySQL 的服务端:
mysql -u root -p 123456
当出现如下输出时,就说明我们成功连接上 MySQL 的服务端,接着就能输入各种 SQL 语句了:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 81
Server version: 5.7.39 MySQL Community Server (GPL)

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
以上的连接,只是连接到 MySQL 服务端,并没有指定连接到哪一个数据库,当然我们可以通过 USE 数据库名称 来切换到指定的数据库,后续的操作都是在该数据库上进行。此处的连接,是一个动作,即 Connect。我们在学习 JDBC 的时候,知道了想要通过 Java 去操作数据库,那么就需要借助 JDBC 来操作。

在这个过程中,我们首先需要加载数据库的驱动,然后建立 Java 程序与某个数据库的连接:
// 堆代码 duidaima.com
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/db_demo";
String username = "root";
String password = "123456";
// 加载驱动
Class.forName(driver);
// 获取该数据库连接(即帮我们创建了一个可以操作db_demo的连接对象)
Connection conn = DriverManager.getConnection(url, username, password);
获取完之后,我们就能通过连接获取相关的 Statement 对象(比如预编译的 PreparedStatement 对象),将我们的 SQL 语句丢给 Statement 对象,通过 Statement 对象执行操作。

操作完毕后就关闭了数据库连接。
conn.close();
这里的连接,是一个动作,也是一个对象,因为 Java 是面向对象的,抽象出了一个连接对象,这个连接对象包含了驱动信息、连接的 URL、DBMS 的用户名和密码,主要表明了此次连接到的是哪个数据库。

池化技术
现在,我们每进行一次相关的数据库操作,就需要经过打开/建立连接,执行相关操作,销毁/关闭连接这么一个过程。对于一个简单的、对数据库的操作不是很频繁的应用来说,问题不大,不会有很明显的性能开销。但是,对于一个经常操作数据库的应用来说,当有许多操作的请求过来时,多次的创建与销毁连接对象,是相当耗费资源的,比如网络带宽,内存,CPU 的运算等等。当然除了耗费资源,创建与销毁也会耗费时间。所以就有了数据库连接池的出现,这种是属于「池化技术」。

池化技术有这样的特点,就是提前准备,然后进行复用。对于数据库连接池,就是提前准备好一定量的连接对象放在一个「池子」中,你可以想象水池,有一定量的水。当有需要的时候,就从这个池子中获取连接对象,然后进行数据库的操作,操作完了后,就把对象放回池子中。这就是所谓的「数据库连接池」。这里也就有种用空间换时间的感觉,通过准备一定量的连接对象,避免由调用者手动去打开和关闭连接,进而提高效率。

自己实现一个数据库连接池
选择你喜欢的一个地方新建一个类和一个配置文件,我将这两个东西放在了同一个目录下:
db.properties:
# 堆代码 duidaima.com
# 数据库相关配置
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db_one_demo
username=root
password=123456
# 初始化的数据库连接池大小
initialPoolSize=5
ConnectionPool.java:

/**
 * @author god23bin
 * @description 简单的数据库连接池
 */
public class ConnectionPool {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;

    /**
     * 使用一个List来存放连接,将这个List作为连接池
     **/
    private static List<Connection> connectionPool;

    /**
     * 标记对应的连接是否被使用,是为 true,否为 false
     **/
    private static List<Boolean> usedConnections;

    /**
     * 连接池大小,即池子中连接的个数
     **/
    private static int initialPoolSize;

    // 读取配置文件只需要一次就够了,所以用static代码块
    static {
        //读取文件配置
        InputStream inputStream = null;
        Properties properties = new Properties();
        try {
            // 如果你的是 Spring Boot 应用,db.properties 放在 resource 目录下,则可以通过 ClassPathResource 来获取这个配置文件
            // inputStream = new ClassPathResource("db.properties").getInputStream();
            inputStream = ConnectionPool.class.getClassLoader().getResourceAsStream("db.properties");


            properties.load(inputStream);
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            initialPoolSize = Integer.parseInt(properties.getProperty("initialPoolSize"));

            connectionPool = new ArrayList<>(initialPoolSize);
            usedConnections = new ArrayList<>(initialPoolSize);
            // 加载驱动
            Class.forName(driver);
            // 创建连接并将连接放到List集合中,标记为未被使用
            for (int i = 0; i < initialPoolSize; i++) {
                Connection connection = DriverManager.getConnection(url, username, password);
                connectionPool.add(connection);
                usedConnections.add(false);
            }

        } catch (IOException | SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     * @return java.sql.Connection 返回连接对象
     **/
    public synchronized Connection getConnection() throws SQLException {
        // 判断是否有空闲的连接可用,有的话就标记为使用中,接着返回这个连接
        for (int i = 0; i < initialPoolSize; i++) {
            if (!usedConnections.get(i)) {
                usedConnections.set(i, true);
                return connectionPool.get(i);
            }
        }

        // 如果没有可用的连接,那么创建一个新的连接,把它加入到池中,并返回,简单处理,这里的创建并没有上限
        Connection connection = DriverManager.getConnection(url, username, password);
        connectionPool.add(connection);
        usedConnections.add(true);
        initialPoolSize++;
        return connection;
    }

    /**
     * 释放连接,将其标记为未使用
     * @param connection 连接
     **/
    public synchronized void releaseConnection(Connection connection) {
        int index = connectionPool.indexOf(connection);
        usedConnections.set(index, false);
    }
}
目前我知道的开源的数据库连接池有 DBCP、C3P0,还有阿里的 Druid。

数据源
数据源(Data Source),即数据的来源。在咱们开发的应用中,数据可以来源于网络,也可以来源于本地的文件,还可以来源于数据库。简而言之,数据源指定了数据从哪里来。换句话说,数据源是指存储数据的位置。在 Java 中,有一个 javax.sql.DataSource 接口,这个接口定义了一组获取数据库连接的方法。
Connection getConnection() throws SQLException
Connection getConnection(String username, String password) throws SQLException
以上,就是所谓的数据源。

数据源和连接池的关系
有的人会把数据源等同于连接池,那到底是不是呢?从概念上看,明显不是一个东西,数据源是数据来源,连接池则是连接的缓存池,用于存储和管理数据库连接。我认为,出现这种看法是因为我们在配置数据源的时候,把连接池也进行了相关的配置。所以才会把数据源等同于连接池。不过,虽然不是同个东西,但是数据源和连接池是紧密相关的,它们一起协同工作来管理数据库连接并提供访问数据库的功能。

在日常开发中,数据源除了指数据来自哪里,还可以有其他信息!对于数据源对象来说,它定义了数据库连接参数以及连接数据库所需的所有信息,例如数据库服务器的地址、用户名和密码等。有的连接池,会从数据源对象中获取连接参数并使用它们来创建和管理数据库连接,就比如当我们在项目中使用开源的数据库连接池的时候,就需要进行相关的配置。对于开源的数据库连接池,它们都具有实现 Java 的标准数据源接口 javax.sql.DataSource 的类,可以直接使用。

以 Druid 为例,这个类是 com.alibaba.druid.pool.DruidDataSource,可以用于创建和管理数据库连接。在我看来,Druid 是一个既包含数据源功能又包含连接池功能的开源项目。它可以用作数据源,通过配置和管理连接池来提供访问数据库的功能。Druid 提供了一组高效的连接池和监控工具,可用于管理和监控数据库连接。
https://github.com/alibaba/druid/wiki/DruidDataSource配置

这里给出 Druid 的一些配置:
 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
     <!-- 基本属性 url、user、password -->
     <property name="url" value="${jdbc_url}" />
     <property name="username" value="${jdbc_user}" />
     <property name="password" value="${jdbc_password}" />

     <!-- 配置初始化大小、最小、最大 -->
     <property name="initialSize" value="5" />
     <property name="minIdle" value="10" />
     <property name="maxActive" value="20" />
     
     <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
     <property name="timeBetweenEvictionRunsMillis" value="2000" />
     
     <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
     <property name="minEvictableIdleTimeMillis" value="600000" />
     <property name="maxEvictableIdleTimeMillis" value="900000" />

 </bean>
从上面的配置中可以看到,我们在这里进行了数据源配置,这里不仅仅配置了连接对象连接的是哪一个数据库,它的用户名和用户密码是多少,还配置了数据库连接池的初始化大小、最小和最大连接数等属性。

总结
一开始,我主要说明了数据库和数据库管理系统(DBMS)的区别。虽然我们通常会将 MySQL、Oracle、PostgreSQL 等软件称为数据库,但它们实际上是一种可以操作和管理数据库的软件,而真正的数据库是指存储数据的仓库。

接着,讲了什么是数据库连接池,数据库连接池是一种池化技术,它可以提前准备好一定数量的连接对象并将它们放在一个「池子」中。这些连接对象可以被重复使用,当需要进行数据库操作时,可以从连接池中获取连接对象并执行相关操作。执行完毕后,连接对象会被放回到池子中,以供后续的操作使用。这种技术可以避免频繁地创建和销毁连接对象,从而提高应用程序的性能和效率。

最后,讲了什么是数据源以及它与连接池的关系,它们两者是不同的,或者说有这么三个词:「数据源」、「数据库连接池」、「数据源对象」。单独说数据源,那么就顾名思义,数据的来源。数据库连接池,用于管理连接的,方便连接的复用,提升效率。数据源对象,它包含了连接池的配置,也配置了数据源,即数据从哪里来。

以上,也不知道我又没有说清楚,欢迎大家评论!
用户评论