Java开发笔记(一百五十)C3P0连接池的用法

JDBC 既制定统一标准兼容了多种数据库,又利用预报告堵上了 SQL 注入漏洞,照理说已经很完善了,可是人算不如天算,它在性能方面不尽如人意。问题出在数据库连接的管理上,按照正常流程,每次操作完数据库,都要关闭连接,无论是代码里手工关闭,还是由 try 语句自动关闭。如果没有及时关闭数据库连接,就会长时间占用有限的数据库内存,致使无谓的系统资源浪费。然而频繁开关数据库连接也有毛病,因为每次获取操作都要 CPU 处理,经常连接数据库会加重 CPU 的负担。看来内存与 CPU 像是一对难兄难弟,不管怎么做都会影响其中一个,正所谓鱼与熊掌不可兼得。
其实连接跟线程的情况相似,线程也很头疼频繁创建导致的资源开销,为此 Java 早早就设计了线程池机制,事先在一个池子中容纳若干线程,需要使用线程时便从中挑一根线程执行任务,任务做完再归还线程,如此实现了线程资源的循环利用,有效提高了系统的整体运行效率。既然线程们组建了线程池这个大家庭,那么连接们能否也组成连接池的大家庭呢?Java 固然自带了线程池工具,却未能推出类似的连接池工具,于是各种第三方的连接池蜂拥而起,例如 DBCP、C3P0、Proxool 等等,其中应用广泛的当数 C3P0。
C3P0 是一个开源的数据库连接池,它支持 JDBC3 规范和 JDBC2 的标准扩展。若要在 Java 工程中运用 C3P0,得先导入它的 jar 包,比如 c3p0-0.9.5.4.jar,同时还要导入该 jar 包依赖的 mchange-commons-java-0.2.16.jar,也就是一共导入两个 jar 文件。使用 C3P0 很简单,掌握 ComboPooledDataSource 类的用法就够了,该类的常见方法说明如下:
setDriverClass:设置连接池的数据库驱动。
setJdbcUrl:设置数据库的连接地址。
setUser:设置数据库的用户名。
setPassword:设置数据库的密码。
setMaxPoolSize:设置连接池大小的上限。
setMinPoolSize:设置连接池大小的下限。
setInitialPoolSize:设置连接池的初始大小。
setMaxStatements:设置报告的最大个数。
setCheckoutTimeout:设置获取连接的等待时间,单位毫秒。当连接池中的所有连接都被占用的时候,新请求想获取连接就必须等待,等待现有连接被释放后才能获取空闲连接。默认为 0 表示一直等待下去。
setMaxIdleTime:设置最大空闲时间,单位秒。如果某个连接超过该时间仍未使用,则会被自动回收。默认为 0 表示不判断是否超时,也就是永不回收。
getConnection:从连接池中获取一个连接。
close:关闭连接池。
引入连接池之后,完整的数据库操作流程分解成了两大步骤:初始化连接池、从连接池中取出一个连接处理,下面分别予以介绍。
1、初始化连接池
该步骤首先创建 C3P0 连接池的对象,再依次调用相关方法设置详细的参数信息,包括数据库驱动、连接地址、用户名、密码,以及与连接池有关的规格参数。下面是初始化 C3P0 连接池的代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static ComboPooledDataSource dataSource; // 声明C3P0连接池的对象
// 初始化连接池
private static void initDataSource() {
    dataSource = new ComboPooledDataSource(); // 创建C3P0连接池
    try {
        dataSource.setDriverClass(driver_class); // 设置连接池的数据库驱动
    } catch (PropertyVetoException e) {
        e.printStackTrace();
    }
    dataSource.setJdbcUrl(dbUrl); // 设置数据库的连接地址
    dataSource.setUser(dbUserName); // 设置数据库的用户名
    dataSource.setPassword(dbPassword); // 设置数据库的密码
    dataSource.setMaxPoolSize(10); // 设置连接池大小的上限
    dataSource.setMinPoolSize(1); // 设置连接池大小的下限
    dataSource.setInitialPoolSize(3); // 设置连接池的初始大小
}

  

2、从连接池中取出一个连接处理
除了一开始调用连接池的 getConnection 获取连接之外,该步骤剩余的操作过程与 JDBC 原有流程保持一致,即获得数据库连接之后,同样要创建连接的报告,然后命令报告执行 SQL 语句。下面是通过连接池操作数据库的代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 显示性别分组
private static void showRecordGroupBySex() {
    String sql = "select sex,count(1) count from teacher group by sex order by sex asc";
    // 从连接池中获取连接、创建连接的报告、命令报告执行指定的SQL语句
    try (Connection conn = dataSource.getConnection();
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql)) {
        while (rs.next()) { // 循环遍历结果集里面的所有记录
            int sex = rs.getInt("sex"); // 获取指定字段的整型值
            int count = rs.getInt("count"); // 获取指定字段的整型值
            String desc = String.format("%s老师有%d位;", sex==0 ? "男" : "女", count);
            System.out.print(desc);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

 

整合连接池的初始化和具体操作的代码,运行包含整合代码之内的测试程序,观察如下日志可知 C3P0 连接池正常工作。

1
男老师有2位;女老师有3位;  



更多 Java 技术文章参见《Java 开发笔记(序)章节目录