JavaWeb学习笔记(十七)—— 数据库连接池

一、数据库连接池概述

1.1 为什么使用数据库连接池

  如果用户每次请求都向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天 10 万访问量,数据库服务器就需要创建 10 万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。如下图所示:

  

1.2 数据库连接池是什么

  数据库连接是一种关键的有限的昂贵的资源, 这一点在多用户的网页应用程序中体现的尤为突出. 对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性, 影响到程序的性能指标。数据库连接池正式针对这个问题提出来的。

  数据库连接池负责分配, 管理和释放数据库连接, 它允许应用程序重复使用一个现有的数据库连接, 而不是重新建立一个。如下图所示:

  

  有了池,我们就不用自己来创建 Connection,而是通过池来获取 Connection 对象。当使用完 Connection 后,调用 Connection 的 close() 方法也不会真的关闭 Connection,而是把 Connection“归还”给池。池就可以再利用这个 Connection 对象了。

       数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的. 无论这些数据库连接是否被使用, 连接池都将一直保证至少拥有这么多的连接数量. 连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数, 当应用程序向连接池请求的连接数超过最大连接数量时, 这些请求将被加入到等待队列中.

       数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:

  1. 最小连接数: 是连接池一直保持的数据库连接, 所以如果应用程序对数据库连接的使用量不大, 将会有大量的数据库连接资源被浪费.
  2. 最大连接数: 是连接池能申请的最大连接数, 如果数据库连接请求超过次数, 后面的数据库连接请求将被加入到等待队列中, 这会影响以后的数据库操作
  3. 如果最小连接数与最大连接数相差很大: 那么最先连接请求将会获利, 之后超过最小连接数量的连接请求等价于建立一个新的数据库连接. 不过, 这些大于最小连接数的数据库连接在使用完不会马上被释放, 他将被放到连接池中等待重复使用或是空间超时后被释放.

二、自定义数据库连接池

2.1 初始版本

【实现思路】  

  1. 编写连接池需实现 java.sql.DataSource 接口,并重写接口中的 getConnection() 方法
  2. 提供一个集合,用于存放连接,因为移除 / 添加操作过多,所以选择 LinkedList
  3. 在静态代码块中为连接池初始化 5 个连接
  4. 程序如果需要连接,调用实现类的 getConnection(),从连接池(容器 List)获得连接。为保证当前连接只能提供一个线程使用,需要将连接先从连接池中移除。
  5. 使用完连接,要释放资源时,不执行 close() 方法,而是将连接添加到连接池中

【代码编写】

public class MyDataSource implements DataSource {
    //1. 创建 1 个容器用于存储 Connection 对象
    private static LinkedList<Connection> pool = new LinkedList<Connection>();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">2.创建5个连接放到容器中去</span>
<span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)">{
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; 5; i++<span style="color: rgba(0, 0, 0, 1)">) {
        Connection connection </span>=<span style="color: rgba(0, 0, 0, 1)"> JdbcUtils.getConnection();
        pool.add(connection);
    }
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 重写获取连接的方法
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Connection getConnection() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SQLException {
    Connection connection </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">3.使用前先判断</span>
    <span style="color: rgba(0, 0, 255, 1)">if</span> (pool.size() == 0<span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">4.池子里面没有,我们再创建一些</span>
        <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; 5; i++<span style="color: rgba(0, 0, 0, 1)">) {
            connection </span>=<span style="color: rgba(0, 0, 0, 1)"> JdbcUtils.getConnection();
            pool.add(connection);
        }
    }
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">5.从池子里面获取一个连接对象Connection</span>
    connection = pool.remove(0<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> connection;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 归还连接对象到连接池中去
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> backConnection(Connection connection) {
    pool.add(connection);
}

...
}

【测试】

@Test
public void testAddUser() {
    Connection conn = null;
    PreparedStatement pstmt = null;
    // 1. 创建自定义连接池对象
    MyDataSource dataSource = new MyDataSource();
    try {
        // 2. 从池子中获取连接
        conn = dataSource.getConnection();
        String sql = "insert into t_user values(?,?)";
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, 3);
        pstmt.setString(2, "张三");
        int num = pstmt.executeUpdate();
        if (num > 0) {
            System.out.println("添加成功");} else {
            System.out.println("添加失败");}
    } catch (Exception e) {e.printStackTrace();
    }finally {dataSource.backConnection(conn);
    }
}

2.2 方法增强——装饰者模式

【需求】

  上述自定义连接池中存在严重问题,用户调用 getConnection()获得连接后,必须使用 backConnection() 方法进行连接的归还,如果用户调用 conn.close() 将连接真正的释放,连接池中将出现无连接可用。

  此时我们希望,即使用户调用了 close()方法,连接仍归还给连接池。close() 方法原有功能是释放资源,我们期望的功能是将连接归还给连接池。说明 close()方法没有我们希望的功能,我们将对 close() 方法进行增强,从而实现将连接归还给连接池的功能。

【方法增强总结】

1. 继承:

  • 子类继承父类,将父类的方法进行复写,从而进行增强。
  • 使用前提:必须有父类,且存在继承关系。

2. 装饰者模式:

  • 此设计模式专门用于增强方法。
  • 使用前提:必须有接口

3. 动态代理:

  • 在运行时动态的创建代理类,完成增强操作。与装饰者相似
  • 使用前提:必须有接口
  • 难点:需要反射技术

【装饰者设计模式】

设计模式:专门为解决某一类问题,而编写的固定格式的代码。

固定结构:接口 A,已知实现类 C,需要装饰者创建代理类 B

实现步骤:

  1. 创建类 B,并实现接口 A
  2. 提供类 B 的构造方法,参数类型为 A,用于接收 A 接口的其他实现类(C)
  3. 给类 B 添加类型为 A 的成员变量,用于存放 A 接口的其他实现类
  4. 增强需要的方法
  5. 实现不需要增强的方法,方法体重调用成员变量存放的其他实现类对应的方法
A a = ...C;
B b = new B(a);
class B implements A{
    private A a;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> B(A,a){
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.a =<span style="color: rgba(0, 0, 0, 1)"> a;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 增强的方法</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> close(){
    
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不需要增强的方法</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> commit(){
    </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.a.commit();
}

}

2.3 使用装饰者模式增强连接池

 【装饰类】

//1. 实现同一个接口 Connection
public class MyConnection implements Connection {
    //3. 定义一个变量
    private Connection conn;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> LinkedList&lt;Connection&gt;<span style="color: rgba(0, 0, 0, 1)"> pool;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">2.编写一个构造方法(参数使用了面向对象的多态特性)</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> MyConnection(Connection conn,LinkedList&lt;Connection&gt;<span style="color: rgba(0, 0, 0, 1)"> pool) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.conn =<span style="color: rgba(0, 0, 0, 1)"> conn;
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.pool =<span style="color: rgba(0, 0, 0, 1)"> pool;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">4.书写需要增强的方法</span>

@Override
public void close() throws SQLException {
pool.add(conn);
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 5.实现不需要增强的方法
 * 注意:此方法必须覆盖!否则会出现空指针异常!!!
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> PreparedStatement prepareStatement(String sql) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SQLException {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> conn.prepareStatement(sql);
}</span><span style="color: rgba(0, 0, 0, 1)">
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> commit() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SQLException {

}</span><span style="color: rgba(0, 0, 0, 1)">


  ...
}

【使用装饰类】

public class MyDataSource1 implements DataSource {
    //1. 创建 1 个容器用于存储 Connection 对象
    private static LinkedList<Connection> pool = new LinkedList<Connection>();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">2.创建5个连接放到容器中去</span>
<span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)">{
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; 5; i++<span style="color: rgba(0, 0, 0, 1)">) {
        Connection conn </span>=<span style="color: rgba(0, 0, 0, 1)"> JdbcUtils.getConnection();
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">放入池子中connection对象已经经过改造了</span>
        MyConnection myconn = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyConnection(conn, pool);
        pool.add(myconn);
    }
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 重写获取连接的方法
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Connection getConnection() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SQLException {
    Connection conn </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">3.使用前先判断</span>
    <span style="color: rgba(0, 0, 255, 1)">if</span> (pool.size() == 0<span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">4.池子里面没有,我们再创建一些</span>
        <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i &lt; 5; i++<span style="color: rgba(0, 0, 0, 1)">) {
            conn </span>=<span style="color: rgba(0, 0, 0, 1)"> JdbcUtils.getConnection();
            </span><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">放入池子中connection对象已经经过改造了</span>

MyConnection myconn = new MyConnection(conn, pool);
pool.add(myconn);
}
}
//5. 从池子里面获取一个连接对象 Connection
conn = pool.remove(0);
return conn;
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 归还连接对象到连接池中去
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> backConnection(Connection connection) {
    pool.add(connection);
}

...
}

【测试】

/**
 * 使用改造过的 connection
 */
@Test
public void testAddUser1() {
    Connection conn = null;
    PreparedStatement pstmt = null;
    // 1. 创建自定义连接池对象
    DataSource dataSource = new MyDataSource1();
    try {
        // 2. 从池子中获取连接
        conn = dataSource.getConnection();
        String sql = "insert into t_user values(?,?)";
        //3. 必须在自定义的 connection 类中重写 prepareStatement(sql) 方法
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, 6);
        pstmt.setString(2, "王五");
        int num = pstmt.executeUpdate();
        if (num > 0) {
            System.out.println("添加成功");} else {
            System.out.println("添加失败");}
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
        //这里的 connection 是已经经过增强的, 增强后的 conn.close() 就是将连接归还给连接池
        JdbcUtils.release(conn, pstmt, null);}
}

三、DBCP 连接池

  DBCP 是 Apache 软件基金组织下的开源连接池实现,在企业开发中也比较常见,它是 tomcat 内置的连接池

3.1 导包

  • Commons-dbcp.jar:连接池的实现
  • Commons-pool.jar:连接池实现的依赖库

3.2 提供配置文件

  • 配置文件名称:*.properties
  • 配置文件位置:任意,建议 src(classpath/ 类路径)
#基本配置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb1
username=root
password=123

#初始化池大小,即一开始池中就会有 10 个连接对象
默认值为 0
initialSize=0

#最大连接数,如果设置 maxActive=50 时,池中最多可以有 50 个连接,当然这 50 个连接中包含被使用的和没被使用的(空闲)
#你是一个包工头,你一共有 50 个工人,但这 50 个工人有的当前正在工作,有的正在空闲
#默认值为 8,如果设置为非正数,表示没有限制!即无限大
maxActive
=8

#最大空闲连接
#当设置 maxIdle=30 时,你是包工头,你允许最多有 20 个工人空闲,如果现在有 30 个空闲工人,那么要开除 10 个
#默认值为 8,如果设置为负数,表示没有限制!即无限大
maxIdle
=8

#最小空闲连接
#如果设置 minIdel=5 时,如果你的工人只有 3 个空闲,那么你需要再去招 2 个回来,保证有 5 个空闲工人
#默认值为 0
minIdle
=0

#最大等待时间
#当设置 maxWait=5000 时,现在你的工作都出去工作了,又来了一个工作,需要一个工人。
#这时就要等待有工人回来,如果等待 5000 毫秒还没回来,那就抛出异常
#没有工人的原因:最多工人数为 50,已经有 50 个工人了,不能再招了,但 50 人都出去工作了。
#默认值为
-1,表示无限期等待,不会抛出异常。
maxWait
=-1

#连接属性
#就是原来放在 url 后面的参数,可以使用 connectionProperties 来指定
#如果已经在 url 后面指定了,那么就不用在这里指定了。
#useServerPrepStmts=true,MySQL 开启预编译功能
#cachePrepStmts
=true,MySQL 开启缓存 PreparedStatement 功能,
#prepStmtCacheSize
=50,缓存 PreparedStatement 的上限
#prepStmtCacheSqlLimit
=300,当 SQL 模板长度大于 300 时,就不再缓存它
connectionProperties
=useUnicode=true;characterEncoding=UTF8;useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=50;prepStmtCacheSqlLimit=300

#连接的默认提交方式
#默认值为 true
defaultAutoCommit=true

#连接是否为只读连接
#Connection 有一对方法:setReadOnly(boolean)和 isReadOnly()
#如果是只读连接,那么你只能用这个连接来做查询
#指定连接为只读是为了优化!这个优化与并发事务相关!
#如果两个并发事务,对同一行记录做增、删、改操作,是不是一定要隔离它们啊?
#如果两个并发事务,对同一行记录只做查询操作,那么是不是就不用隔离它们了?
#如果没有指定这个属性值,那么是否为只读连接,这就由驱动自己来决定了。即 Connection 的实现类自己来决定!
defaultReadOnly
=false

#指定事务的事务隔离级别
#可选值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
#如果没有指定,那么由驱动中的 Connection 实现类自己来决定
defaultTransactionIsolation=REPEATABLE_READ

3.3 编写工具类

public class DBCPUtils {
    /**
     * 在 java 中,编写数据库连接池需实现 java.sql.DataSource 接口,每一种数据库连接池都是 DataSource 接口的实现
     * DBCP 连接池就是 java.sql.DataSource 接口的一个具体实现
     */
    private static DataSource dataSource = null;
    //在静态代码块中创建数据库连接池
    static{
        try {
            //加载 dbcp.properties 配置文件
            InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("dbcp.properties");
            Properties prop = new Properties();
            prop.load(in);
            //创建数据源
            dataSource = BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {e.printStackTrace();
        }
    }
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 从数据源中获取数据库连接
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> Connection getConnection() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SQLException {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> dataSource.getConnection();
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 释放资源
 * 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> release(Connection conn, Statement st, ResultSet rs) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (rs != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            rs.close();
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
            e.printStackTrace();
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (st != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            st.close();
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
            e.printStackTrace();
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (conn != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            conn.close();
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
            e.printStackTrace();
        }
    }
}

}

3.4 测试 DBCP 数据源

public class DBCPTest {
    @Test
    public void addUser() {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            // 获取数据库连接
            conn = DBCPUtils.getConnection();
            String sql = "insert into t_user values(?,?)";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, 8);
            pstmt.setString(2, "刘备");
            int num = pstmt.executeUpdate();
            if (num > 0) {
                System.out.println("添加成功");}
        } catch (Exception e) {e.printStackTrace();
        }
    }
}

四、C3P0 连接池

  C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0 数据源在项目开发中使用得比较多。

  c3p0 与 dbcp 区别:
  • dbcp 没有自动回收空闲连接的功能
  • c3p0 有自动回收空闲连接功能

4.1 导包

  • c3p0-0.9.2-pre1.jar
  • mchange-commons-0.2.jar

  如果操作的是 Oracle 数据库,那么还需要导入 c3p0-oracle-thin-extras-0.9.2-pre1.jar

4.2 添加配置文件

  • 配置文件名称:c3p0-config.xml(必须是这个)
  • 配置文件位置:src(必须放在类路径下)
  • 配置文件内容:
    <?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">root</property>
            <!--池参数配置-->
            <!--如果池中数据连接不够时一次增长多少个-->
            <property name="acquireIncrement">3</property>
            <!--初始化连接数-->
            <property name="initialPoolSize">10</property>
            <!--最小连接数-->
            <property name="minPoolSize">2</property>
            <!--最大连接数-->
            <property name="maxPoolSize">10</property>
        </default-config>
        <!--专门为 Oracle 提供的配置信息-->
        <named-config name="oracle-config">
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="user">root</property>
            <property name="password">123</property>
            <property name="acquireIncrement">3</property>
            <property name="initialPoolSize">10</property>
            <property name="minPoolSize">2</property>
            <property name="maxPoolSize">10</property>
        </named-config>
    </c3p0-config>

4.3 编写工具类 

public class C3P0Utils {
    private static ComboPooledDataSource dataSource = null;
    //在静态代码块中创建数据库连接池
    static{
        try {
            // 通过代码创建 C3P0 连接池
            /*dataSource = new ComboPooledDataSource();
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
            dataSource.setUser("root");
            dataSource.setPassword("root");
            dataSource.setInitialPoolSize(10);
            dataSource.setMinPoolSize(5);
            dataSource.setMaxPoolSize(20);*/
        <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">通过读取C3P0的xml配置文件创建数据源,C3P0的xml配置文件c3p0-config.xml必须放在src目录下
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用C3P0的默认配置来创建数据源</span>
        dataSource = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ComboPooledDataSource();
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用名为oracle-config的配置来创建数据源
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> dataSource = new ComboPooledDataSource("oracle-config");</span>
    } <span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
        e.printStackTrace();
    }
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 从数据源中获取数据库连接
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> Connection getConnection() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SQLException {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> dataSource.getConnection();
}

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
 * 释放资源
 * 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> release(Connection conn, Statement st, ResultSet rs) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (rs != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            rs.close();
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
            e.printStackTrace();
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (st != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            st.close();
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
            e.printStackTrace();
        }
    }
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (conn != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
        </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            conn.close();
        } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (SQLException e) {
            e.printStackTrace();
        }
    }
}

}

4.4 测试 C3P0 数据源

@Test
public void addUser() {
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
        // 获取数据库连接
        conn = C3P0Utils.getConnection();
        String sql = "insert into t_user values(?,?)";
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, 9);
        pstmt.setString(2, "关羽");
        int num = pstmt.executeUpdate();
        if (num > 0) {
            System.out.println("添加成功");}
    } catch (Exception e) {e.printStackTrace();
    }
}

五、Tomcat 配置连接池

  在实际开发中,我们有时候还会使用服务器提供给我们的数据库连接池,比如我们希望 Tomcat 服务器在启动的时候可以帮我们创建一个数据库连接池,那么我们在应用程序中就不需要手动去创建数据库连接池,直接使用 Tomcat 服务器创建好的数据库连接池即可。要想让 Tomcat 服务器在启动的时候帮我们创建一个数据库连接池,那么需要简单配置一下 Tomcat 服务器。

5.1 JNDI 技术简介

  JNDI(Java Naming and Directory Interface),Java 命名和目录接口,它对应于 J2SE 中的 javax.naming 包,
  这套 API 的主要作用在于:它可以把 Java 对象放在一个容器中(JNDI 容器),并为容器中的 java 对象取一个名称,以后程序想获得 Java 对象,只需 通过名称检索即可。其核心 API 为 Context,它代表 JNDI 容器,其 lookup 方法为检索容器中对应名称的对象。

  Tomcat 服务器创建的数据源是以 JNDI 资源的形式发布的,所以说在 Tomat 服务器中配置一个数据源实际上就是在配置一个 JNDI 资源,通过查看 Tomcat 文档,我们知道使用如下的方式配置 tomcat 服务器的数据源:

<Context>
  <Resource name="jdbc/datasource" auth="Container"
            type="javax.sql.DataSource" username="root" password="root"
            driverClassName="com.mysql.jdbc.Driver" 
            url="jdbc:mysql://localhost:3306/test"
            maxActive="8" maxIdle="4"/>
</Context>

  服务器创建好数据源之后,我们的应用程序又该怎么样得到这个数据源呢,Tomcat 服务器创建好数据源之后是以 JNDI 的形式绑定到一个 JNDI 容器中的,我们可以把 JNDI 想象成一个大大的容器,我们可以往这个容器中存放一些对象,一些资源,JNDI 容器中存放的对象和资源都会有一个独一无二的名称,应用程序想从 JNDI 容器中获取资源时,只需要告诉 JNDI 容器要获取的资源的名称,JNDI 根据名称去找到对应的资源后返回给应用程序。我们平时做 javaEE 开发时,服务器会为我们的应用程序创建很多资源,比如 request 对象,response 对象,服务器创建的这些资源有两种方式提供给我们的应用程序使用:第一种是通过方法参数的形式传递进来,比如我们在 Servlet 中写的 doPost 和 doGet 方法中使用到的 request 对象和 response 对象就是服务器以参数的形式传递给我们的。第二种就是 JNDI 的方式,服务器把创建好的资源绑定到 JNDI 容器中去,应用程序想要使用资源时,就直接从 JNDI 容器中获取相应的资源即可。

  对于上面的 name="jdbc/datasource" 数据源资源,在应用程序中可以用如下的代码去获取 

Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/datasource");

  此种配置下,数据库的驱动 jar 文件需放置在 tomcat 的 lib 下

  

5.2 配置 JNDI 资源

  • 在 Web 项目的 WebRoot 目录下的 META-INF 目录创建一个 context.xml 文件
  • 在 context.xml 文件配置 tomcat 服务器的数据源 
<Context>
    <!--name: 指定资源的名称
        factory: 用来创建资源的工厂, 这个值基本上是固定的, 不用修改
        type: 资源的类型
        其他的东西都是资源的参数
    -->
    <Resource
            name="jdbc/datasource"
            factory="org.apache.naming.factory.BeanFactory"
            type="javax.sql.DataSource"
            username="root"
            password="root"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/test"
            maxActive="8"
            maxIdle="4"/>
</Context>

5.3 获取 JNDI 的资源

/**
 * 获取 JNDI 的资源
 *
 */
public class AServlet extends HttpServlet {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> doGet(HttpServletRequest request, HttpServletResponse response)
        </span><span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ServletException, IOException {
    </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
     * 1. 创建JNDI的上下文对象
     </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        
        Context cxt </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> InitialContext();
        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2查询出入口

// Context envContext = (Context)cxt.lookup("java:comp/env");
// 3. 再进行二次查询,找到我们的资源
// 使用的是名称与 <Resource> 元素的 name 对应
// DataSource dataSource = (DataSource)envContext.lookup("jdbc/dataSource");

DataSource dataSource
= (DataSource)cxt.lookup("java:comp/env/jdbc/dataSource");

        Connection con </span>=<span style="color: rgba(0, 0, 0, 1)"> dataSource.getConnection();
        System.out.println(con);
        con.close();
    } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
        </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RuntimeException(e);
    }
}

}

 

 

 

参考:https://www.cnblogs.com/xdp-gacl/p/4002804.html