09_database_09_jdbc-8

第八十四章 数据源(DataSource)与数据库连接池(DB Connection Pool)

1. 简介

我们在前面的章节中介绍了JDBC接口的基本使用方法。在数据查询和访问过程中,我们首先需要从DriverManager中创建一个Connection对象,表示一条与数据库的连接。这样做并没有什么问题,在中小型规模的应用程序中,这种方法能够很好的胜任她的功能和职责。但是,在大型项目中,我们需要更加灵活的设计以应对需求变化。

因此,JDBC在此基础之上,增加了一层抽象层,称为"数据源(DataSource)"。数据源是一个数据库连接的工厂接口(Factory of DB Connections)。换句话说,她的功能是生成数据库连接对象的。

在增加了这样一层数据源抽象层之后,我们能够为JDBC接口提供更为丰富的功能与实现。例如,DataSource常被用于以下几个应用场景。

  1. 在使用DriverManager创建Connection对象时,开发人员需要首先向DriverManager注册驱动信息。但是,当使用DataSource之后,开发人员不需要注册任何的驱动信息了。他们可以使用Java应用程序的查找功能(例如:JNDI, Java Naming and Directory Interface),找到对应的DataSource.
  2. 通过使用DataSource来实现数据库连接池,我们将在第三节详细介绍数据库连接池技术。
  3. 通过使用DataSource能实现自动切换驱动程序,以达到切换数据库的目的。当数据发生变化或者迁移时,DataSource能够适配这一变化,而上层的应用程序无需做出任何改变。
  4. 通过使用DataSource能够实现分布式查询或者分布式事务处理。在这种分布式场景下,Java应用程序可能仅是查询一张表中的内容,但是,DataSource可能从多个数据库服务器中获取相关数据。

这些都是DataSource能为我们带来的好处。我们将在本章中详细介绍DataSource的使用方法和数据库连接池技术。

2. DataSource的基本使用方法

DataSource是javax.sql包中的一个重要接口。它最主要的方法是getConnection(),用于创建一个数据库连接,生成java.sql.Connection对象。无参数的getConnection()使用默认值建立连接;而getConnection(String username, String password)则使用给定的用户名/密码建立连接。

因此,当我们获得了一个DataSource对象之后,就可以直接调用getConnection()方法,建立连接了。获取DataSource对象的常见方法大致有两种,一种是使用JNDI或者类似的技术主动查询DataSource对象。另一种方法是使用Injection技术,让其他模块创建DataSource对象,并插入(Inject)到本程序模块中。如果读者感兴趣的话,可以阅读相关文档。

DataSource ds = ... // 可用上述两种方法中的任意一种创建一个DataSource对象。
java.sql.Connection connection = ds.getConnection();

3. 数据库连接池技术(Database Connection Pool)

在执行任何数据查询或者其他操作之前,需要事先创建数据库连接。然而,建立数据库连接通常是一个非常复杂的过程,需要消耗许多资源和时间。那么,为了加快数据操作的速度,人们设计了数据库连接池技术。数据库连接池技术会事先创建多个数据库连接,并将其放在一个共享池(Sharing Pool)中。当应用程序需要访问数据库时,应用程序可以向数据库连接池申请一个连接使用。在使用完毕之后,应用程序将其返还给数据库连接池,以便于其他模块可以继续使用。

数据库连接池的概念非常简单,直观。那么,我们将在下面的例子中实现一个简单的数据库连接池,以便于更加清晰的讲解其内在逻辑与结构。为了保持示例简单易读,我们省略了一些无关的实现内容。

在LWConnectionPool类中,我们使用了成员变量idle和busy来分别存放空闲和繁忙的数据库连接。这些连接是LWConnection对象。

在LWConnectionPool的构造函数中,我们创建了10个数据库连接对象,存放在idle列表中。当调用getConnection()方法申请连接对象时,可直接从idle列表中取出一个对象,将其添加到busy集合中,并返回给申请者。在使用完毕后,申请者会间接调用releaseConnection()返还该连接对象。在整个过程中,示例并未关闭任何数据库连接对象,仅仅将其返还给数据库连接池。

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Set;
import java.util.TreeSet;
import java.util.List;
import java.util.LinkedList;

public class LWConnectionPool implements DataSource {
    private List<LWConnection> idle = new LinkedList<LWConnection>();
    private Set<LWConnection> busy = new TreeSet<LWConnection>();;

    public LWConnectionPool() {
        Class.forName("org.postgresql.Driver");
        String url = "jdbc:postgresql://localhost/dbname";
        String username = "littlewaterdrop";
        String password = "password";

        // 先准备好10个数据库连接对象
        for (int i = 0; i < 10; i++) {
            Connection connection = DriverManager.getConnection(url, username, password);
            idle.add(new LWConnection(this, connection));
        }
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (idle.isEmpty()) {
            throw new SQLException('Connections are running out.');
        }

        // 从idle中分配一个空闲的对象给申请者
        Connection conn = idle.remove(0);
        busy.add(conn);
        return conn;
    }

    public void releaseConnection(LWConnection conn) {
        // 将返还的对象添加到idle列表中
        if (busy.remove(conn)) {
            idle.add(conn);
        }
    }

    ...
}

LWConnection类是对数据库连接Connection对象的一个简单封装。LWConnection实现了Connection接口是因为当应用程序调用close()方法时,LWConnection能在其成员方法close()中修改默认的行为。LWConnection并不关闭数据库连接,而是将其返还给数据库连接池LWConnectionPool对象。LWConnection使用了成员变量connection,指向真实的数据库连接对象,因为当应用程序调用createStatement()方法创建Statement对象时,或者使用其他数据库相关方法时,LWConnection对象能够将请求传递给对应的数据库连接对象。

import java.sql.Connection;
import java.sql.SQLException;

public class LWConnection implements Connection {
    private LWConnectionPool pool = null;
    private Connection connection = null;

    public LWConnection(LWConnectionPool pool, Connection connection) {
        this.pool = pool;
        this.connection = connection;
    }

    @Override
    public void close() throws SQLException {
        // 将连接返还给数据库连接池LWConnectionPool对象。
        this.pool.releaseConnection(this);
    }

    @Override
    public Statement createStatement() throws SQLException {
        // 将方法调用直接传递给真正的数据库连接对象
        return this.connection.createStatement();
    }

    ...
}

在使用数据库连接时,上述的实现细节对使用者是透明的。使用者并不知道在使用完毕后,Connection对象会被返还给数据库连接池。

public class LWDbConnectionPoolExample {
    public static void main(String[] args) {
        // 创建数据库连接池
        LWConnectionPool pool = new LWConnectionPool();

        // 从数据库连接池中申请连接对象,在其上进行数据操作
        try (Connection connection = pool.getConnection()) {
            try (Statement stmt = connection.createStatement()) {
                ResultSet result = stmt.executeQuery("select name, age, gender from student;");
                ...
            }
        }
    }
}

4. 第三方JDBC数据库连接池代码库

在Java应用程序中,常用的JDBC数据库连接池代码库有Apache Commons DBCP2C3P0Apache Tomcat JDBCHikariCP。她们都能够提供较稳定的服务。如果读者对她们的使用方法感兴趣的话,可参考她们的技术文档。

5. 小结

本章介绍了JDBC接口中的DataSource接口。DataSource为整个JDBC接口增加了一个抽象层,使得JDBC的实现更加丰富、更加灵活。使用DataSource的一个常见应用是实现数据库连接池。数据库连接池技术缓存了多个数据库连接;当应用程序需要时,可直接向数据库连接池申请,无需实时的创建一个新的连接。这样做能节省连接创建所消耗的时间,提升数据查询的效率。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.