Java中使用c3p0建立MySQL数据库连接池


=Start=

缘由:

在学习Java的过程中不断用文章进行整理总结(常用功能的Java实现),争取早日能较为熟练的使用Java进行开发。

本来用c3p0建立数据库连接池这部分内容只想用一篇文章的一部分来进行代码样例说明的,但是没想到在网上搜到的一些介绍性文章或多或少都有些问题,对于我这样的初学者太不友好,一些本来很简单的问题就因为他们文章中的一句话让我走了不少弯路,所以感觉有必要单独记录一下这个过程,方便自己参考、查阅。

正文:

参考解答:
1、c3p0的基本概念

c3p0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。

2、c3p0的几种配置方式

c3p0的配置方式分为三种,分别是:

  1. 使用c3p0-config.xml文件(文件名称固定);
  2. 使用c3p0.properties文件(文件名称固定);
  3. 用setters一个个地设置各个配置项。

特别说明:对于用IDEA建立的Maven项目来说,上面的c3p0-config.xml和c3p0.properties文件,要放在项目的「resources」目录下,而不是一些文章里说的「src」目录下,否则会出现:

java.sql.SQLException: Connections could not be acquired from the underlying database!

Caused by: com.mchange.v2.resourcepool.CannotAcquireResourceException: A ResourcePool could not acquire a resource from its primary factory or source.

的错误。

3、不同方式的配置样例

①c3p0-config.xml文件的配置方式

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 默认配置,如果没有指定则使用这个配置 -->
    <default-config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <property name="checkoutTimeout">3000</property>
        <property name="idleConnectionTestPeriod">30</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
    </default-config>

    <!-- 命名的配置 -->
    <named-config name="mysqlConnection">
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <property name="initialPoolSize">25</property>
        <property name="maxPoolSize">50</property>
        <property name="minPoolSize">5</property>
        <property name="acquireIncrement">5</property>
    </named-config>
</c3p0-config>

对应的Java测试代码为:

package com.ixyzero.learn.db;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * Created by ixyzero.com on 2018/6/11.
 */
public class TestConnXml {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static void main(String[] args) {
        try{
            Connection connection = dataSource.getConnection();
            System.out.println("连接数据库成功!");

            // 执行查询语句
            String sql = "select * from websites limit 3";
            PreparedStatement ps = connection.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                System.out.println(rs.getString(1));
            }

            connection.close();
            System.out.println("数据库连接已关闭!");
        }catch(Exception e) {
            e.printStackTrace();
            System.out.println("连接数据库失败!");
        }

    }
}

②c3p0.properties文件的配置方式

c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/test
c3p0.user=root
c3p0.password=123456

注意:采用c3p0.properties文件进行配置的,都要加上前缀「c3p0.」,否则也会出现连不上的错误!

测试代码和上面的相同。

③用setter一个个的设置相关配置项

这个可以通过配置文件存放数据库用户名、密码、连接等信息,也可以直接写在代码中:

package com.ixyzero.learn.db;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * Created by ixyzero.com on 2018/6/11.
 */
public class TestConn {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static void main(String[] args) {
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        try {
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
        } catch (PropertyVetoException e) {
            e.printStackTrace();
            System.out.println("setDriverClass 失败!");
            return;
        }
        dataSource.setInitialPoolSize(5);
        dataSource.setMinPoolSize(1);
        dataSource.setMaxPoolSize(10);
        dataSource.setMaxStatements(50);
        dataSource.setMaxIdleTime(60);
        System.out.println(dataSource);

        try{
            Connection connection = dataSource.getConnection();
            System.out.println("连接数据库成功!");

            // 执行查询语句
            String sql = "select * from websites";
            PreparedStatement ps = connection.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                System.out.println(rs.getString(1));
            }

            connection.close();
            System.out.println("数据库连接已关闭!");
        }catch(Exception e){
            e.printStackTrace();
            System.out.println("连接数据库失败!");
        }
    }

}

&

下面用到的配置文件也要放在项目的「resources」目录下!

public class TestConnProperties {
    private static Properties prop = new Properties();
    private static InputStream in = TestConnProperties.class.getClassLoader().getResourceAsStream("db.properties");
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static void main(String[] args) {
        try {
            prop.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }

        dataSource.setUser(prop.getProperty("username"));
        dataSource.setPassword(prop.getProperty("password"));
        dataSource.setJdbcUrl(prop.getProperty("url"));
        try {
            dataSource.setDriverClass(prop.getProperty("jdbcdriver"));
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }

        //...
    }
}
4、和不使用连接池的情况做对比
package com.ixyzero.learn.db;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * Created by ixyzero.com on 2018/6/11.
 */
public class TestProperties {
    // JDBC 驱动名及数据库 URL
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://localhost:3306/test";
    // 数据库的用户名与密码,需要根据自己的设置
    static final String USER = "root";
    static final String PASS = "123456";

    private static ComboPooledDataSource datasource = new ComboPooledDataSource();

    public static void main(String[] args) {
        // jdbc without using connection pool
        try {
            for (int i = 0; i < 100; i++) {
                long beginTime = System.currentTimeMillis();

                Class.forName(JDBC_DRIVER);

                Connection con = DriverManager.getConnection(DB_URL, USER, PASS);
                String sql = "select * from websites";
                PreparedStatement ps = con.prepareStatement(sql);
                ResultSet rs = ps.executeQuery();
                while (rs.next()) {
                    // System.out.println(rs.getString(1));
                }
                con.close();
                long endTime = System.currentTimeMillis();
                System.out.println(String.format("第%s次, %s (ms)", i, (endTime - beginTime)));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("\n");


        datasource.setUser(USER);
        datasource.setPassword(PASS);
        datasource.setJdbcUrl(DB_URL);
        try {
            datasource.setDriverClass(JDBC_DRIVER);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        datasource.setInitialPoolSize(20);
        datasource.setMinPoolSize(1);
        datasource.setAcquireIncrement(5);
        datasource.setMaxPoolSize(100);
        datasource.setMaxStatements(50);
        datasource.setMaxIdleTime(60);
        // use c3p0 connection pool
        try {
            for (int i = 0; i < 100; i++) {
                long beginTime = System.currentTimeMillis();
                Connection con = datasource.getConnection();
                // 执行查询语句
                String sql = "select * from websites";
                PreparedStatement ps = con.prepareStatement(sql);
                ResultSet rs = ps.executeQuery();
                while (rs.next()) {
                    // System.out.println(rs.getString(1));
                }
                con.close();
                long endTime = System.currentTimeMillis();
                System.out.println(String.format("[c3p0]第%s次, %s (ms)", i, (endTime - beginTime)));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

MySQL在高版本需要指明是否进行SSL连接,未配置useSSL时警告信息正好可以用来显示一次连接。从实际运行日志中可以看出,当使用连接时,程序在启动时直接建立了20个数据库连接(如果不显式指定的话,默认是3个),接下来的数据库操作通过连接池中的连接操作数据库,不再新建连接。而不使用连接池时,每一次数据库操作都建立了一次数据库连接。

从上面的测试也可以看出——使用连接池的操作耗时明显短于每次新建连接的操作,所以,对于存在多个连接的情况下,使用连接池可以带来极大的效率提升。

参考链接:

=END=

, ,

《 “Java中使用c3p0建立MySQL数据库连接池” 》 有 7 条评论

  1. JDBC超时问题全面分析
    https://benjaminwhx.com/2018/02/01/JDBC%E8%B6%85%E6%97%B6%E9%97%AE%E9%A2%98%E5%85%A8%E9%9D%A2%E5%88%86%E6%9E%90/

    How do I set the query timeout limit?
    https://kodejava.org/how-do-i-set-the-query-timeout-limit/
    `
    Statement stmt = connection.createStatement();

    // Sets the number of seconds the driver will wait for
    // a statement object to execute to the given number of
    // seconds. If the limit is exceeded, an SQLException
    // is thrown.
    stmt.setQueryTimeout(60);
    `
    https://stackoverflow.com/questions/5913569/how-to-set-query-timeout-on-preparedstatement

  2. 为什么参数化SQL查询可以防止SQL注入?
    https://www.zhihu.com/question/52869762/answer/136411127
    `
    预编译之所以能防止sql注入(我在这里只说sql注入这方面),还要从sql的执行方式有关。

    一般来说,sql 是怎么执行的呢?简单说,一个sql 是经过解析器编译并执行,注意这里是一个并字。举一个栗子,校验有没有这个用户的场景sql,
    select count(1) from students where name=’张三’
    上边的数据库执行时,是直接将这句话连带 name=’张三’,一起给编译了,然后执行。假设我的sql 注入语句是
    select count(1) from students where name=’张三’ or ‘1=1’
    即name 参数为张三‘ or ‘1=1 ,这个参数也会被编译器一同编译。

    而使用预编译,数据库是怎么处理的呢?这个步骤是在con.prepareStatement(“”)语句执行的时候,服务器就这个sql发送给了数据库,然后数据库将该sql编译后放入到缓存池中。等到服务器执行execute的时候,传给数据库的 张三‘ or ‘1=1 编译器并不进行编译,而是这找到原来的sql模板,传参,执行。所以, 张三‘ or ‘1=1 只会被数据库当做参数来处理。我这样解释不知道大家是否明白?
    `

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注