7.1 连接池

今天我们将学习如何更专业地使用数据库。现在我们将讨论线程池,或者英文的连接池。

连接到数据库需要一些时间。特别是如果数据库是远程的。如果我们为每个请求都连接到数据库,那么我们的应用程序的响应速度将非常慢。更不用说它会消耗的资源了。

作为此类问题的解决方案,有人提出将连接存储在某个集合中,称为线程池。

当我们请求一个新的连接时,连接池会创建它,关闭时并不会关闭它,而是保存在连接池中。如果我们再次从连接池请求连接,它将给我们一个旧的,而不是创建一个新的。

事实上,该应用程序通过一个特殊的驱动程序工作,该驱动程序是常规 JDBC 驱动程序的包装器,并且具有用于处理池的附加功能。这种情况可以表示如下:

连接池

程序员可以手动设置连接池的设置:活动连接数、超时等。

对于特别极端的情况,您可以编写自己的连接池:一个包含连接列表的类。它将覆盖关闭函数,这会将连接返回到列表,并且会有许多其他好东西,如打开连接计时器。当长时间没有连接时,连接关闭。

7.2* 接口DataSource和ConnectionPoolDataSource

线程池通常与数据源对象协同工作。这个对象可以被认为是远程数据库的抽象。数据源这个名字可以直译为数据源。这是一种暗示。

DataSource 对象用于获取与数据库的物理连接,是 DriverManager 的替代方法。无需注册 JDBC 驱动程序。您只需要设置适当的参数来建立连接和执行 getConnection() 方法.

在本地(直接在应用程序中)创建 DataSource 类型的对象时,连接参数由 JDBC 驱动程序提供者提供的适当方法设置。这些方法不是由 DataSource 接口定义的,因为连接到来自不同供应商的 DBMS 的参数在类型和数量上可能不同(例如,并非所有 DBMS 都需要指定驱动程序或网络协议的类型)。

因此,在使用Oracle DBMS 时,要使用相应的set 和get 方法,就需要获得一个OracleDataSource 类型的对象,该对象是实现了DataSource 接口的同名类的实例。因此,这种创建 DataSource 类型对象的方式使您的代码的可移植性降低,并且对驱动程序的具体实现的依赖性降低。

下面的代码示例说明了 DataSource 类型对象的本地使用。

import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.*;

public class DataSource {
    public static void main(String[] args) {
    	try {
        	OracleDataSource ods = new OracleDataSource();

        	ods.setUser("root");
        	ods.setPassword("secret");
        	ods.setDriverType("thin");
        	ods.setDatabaseName("test");
        	ods.setServerName("localhost");
        	ods.setPortNumber(1521);

        	Connection connection = ods.getConnection();
        	System.out.println("Connection successful!!!");

    	} catch (SQLException se) {
        	se.printStackTrace();
    	}
    	System.out.println("Goodbye!");
	}
}

7.3* JNDI 接口

DataSource 类型对象的全部功能与 JNDI 接口的使用相结合。JNDI 是一种用于大型服务器应用程序的特殊服务(类似于目录),它允许一个服务与另一个服务建立连接。

使用名称和目录服务允许您存储以前由系统管理员使用预定义的连接参数创建的 DataSource 类型的对象。以下是 Sun 开发的一些标准属性(参数)名称:

物业名称 Java数据类型 目的
数据库名称 细绳 数据库名称
服务器名称 细绳 服务器名称
用户 细绳 用户名(ID)
密码 细绳 用户密码
端口号 整数 DBMS 服务器端口号

DataSource 和JNDI 接口的结合在开发基于J2EE 组件技术的多层企业应用程序中起着关键作用。

如果您在应用程序代码中结合使用 DataSource 和 JNDI 接口,则只需从命名和目录服务请求一个 DataSource 类型的对象。在这种情况下,连接的详细信息和依赖于特定驱动程序实现的程序代码对应用程序隐藏在名称和目录服务中存储的对象中。

因此,DataSource 和 JNDI 类型对象的共享涉及两个相互独立执行的步骤:

  1. 您必须使用Context.bind().javax.naming
  2. 使用Context.lookup(). 之后,您可以使用它的方法DataSource.getConnection()来获得与数据库的连接。

以下是一起使用 JNDI 接口和 OracleDataSource 类型对象的示例。

// Create a key JNDI object
Hashtable env = new Hashtable();
env.put (Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
Context ctx = new InitialContext(env);

// Get the DataSource object by its name
DataSource ods = (DataSource) ctx.lookup("myDatabase");

// Get the Connection from the DataSource object
Connection connection = ods.getConnection();
System.out.println("Connection successful!!!");

我们不会分析JNDI 的工作。这超出了本课程的范围。我只想让您知道,这种方法在大型应用程序中非常普遍。如果您以后看到这样的代码,请不要迷路。

它是如何工作的并不重要。您只需要知道,通过 JNDI 服务,您可以获得在服务目录中注册的任何服务的代理对象。这就是您获取 DataSource 对象并使用它来处理数据库的方式。

7.4 连接池示例

让我们回到我们开始的地方——连接池。

Java 程序员在他们的程序中经常使用包含各种连接池实现的库。其中最受欢迎的有以下三种:

  • Apache Commons DBCP
  • C3PO
  • 光CP

Apache 项目是第一个创建良好连接池的项目,它也在 Tomcat 网络服务器内部使用。使用它的一个例子:

public class DBCPDataSource {

    private static BasicDataSource ds = new BasicDataSource();
    static {
    	ds.setUrl("jdbc:h2:mem:test");
    	ds.setUsername("user");
    	ds.setPassword("password");
    	ds.setMinIdle(5);
    	ds.setMaxIdle(10);
    	ds.setMaxOpenPreparedStatements(100);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private DBCPDataSource(){ }
}

第二个非常受欢迎的矿池是C3PO。奇怪的名字,我同意。C3PO 这个名字是参考了星球大战中的 c3po 机器人。CP 也是 Connection Pool 的缩写。

public class C3p0DataSource {

    private static ComboPooledDataSource cpds = new ComboPooledDataSource();
    static {
    	try {
        	cpds.setDriverClass("org.h2.Driver");
        	cpds.setJdbcUrl("jdbc:h2:mem:test");
        	cpds.setUser("user");
        	cpds.setPassword("password");
    	} catch (PropertyVetoException e) {
            // handle the exception
    	}
    }

    public static Connection getConnection() throws SQLException {
    	return cpds.getConnection();
    }

    private C3p0DataSource(){}
}

它的文档可以在官方页面上找到。

最后,我们这个时代最流行的连接池是HikariCP

public class HikariCPDataSource {

    private static HikariConfig config = new HikariConfig();
    private static HikariDataSource ds;

    static {
    	config.setJdbcUrl("jdbc:h2:mem:test");
    	config.setUsername("user");
    	config.setPassword("password");
    	config.addDataSourceProperty("cachePrepStmts", "true");
    	config.addDataSourceProperty("prepStmtCacheSize", "250");
    	config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
    	ds = new HikariDataSource(config);
    }

    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }

    private HikariCPDataSource(){}
}

这是他的官方GitHub页面。

还有一篇关于配置的好文章