JAVA开发工程师(WEB方向) - 03.数据库开发 - 第2章.数据库连接池

第 2 章 -- 数据库连接池

数据库连接池

一般而言,在实际开发中,往往不是直接使用 JDBC 访问后端数据库,而是使用数据库连接池的机制去管理数据库连接,来实现对后端数据库的访问。

建立 Java 应用程序到后端数据库的物理连接:conn = DriverManager.getConnection(...);

虽然这只是一个方法调用,但是在 JDBC 的驱动中,就已经完成了大量客户端与服务器端的交互

这里以 MySQL 数据库为例(MySQL 客户端和 MySQL 服务器端的交互):

1. 客户端发送给服务器端 TCP 请求

2. 服务器端随机生成密码种子返回给客户端

3. 客户端利用密码种子和自己保存的数据库密码按照约定的加密算法计算得到加密密码,将其发送给服务器端进行验证

4. 服务器端验证完成确认链接建立成功

一个 getConnection 方法,就需要了四次网络传输:时间开销大

实际开发中,但用户要访问 Java 程序时,会启动一个线程去处理用户的请求。在处理过程中,如果需要访问后端数据库,则需要创建一个 Connection 对象来建立到后端数据库的物理连接。之后,随着 close 方法被调用,该连接被销毁。业务处理完成后,该线程被释放。但是当用户再次发起一个请求时,又要重新建立新的线程,创建新的 Connection 对象等等...... 花费大量时间在建立连接上,响应时间增长。

--> 连接复用(创建连接 -- 改变为 --> 租借连接)

每个线程在使用数据库连接后,并不立即销毁,而是把连接交给下一个需要访问数据库的线程。多个线程共用到后端数据库的物理连接,实现连接复用

连接池:每个需要访问数据库的线程到连接池中租借到数据库的连接,使用完毕后再归还给连接池,即可实现连接的重复使用。

数据库服务器端在处理我们的服务请求时,都会在服务器端分配一定的资源,比如内存用来保存数据库查询的结果。在请求处理结束以后,释放这些资源。而服务器端的资源是有限的,不能无限制地分配。当同时有多个数据库请求访问数据库时,服务器端能处理的连接数是有限的。当超过最大可分配的资源时,会出现服务器 down 机的故障。同时,过多的连接数对后端数据库的性能也有很大影响。连接数增多,数据库会产生很多锁的检测,加大数据库服务器端的资源消耗。为了限制并发访问的连接数,数据库服务器端会设置最大的并发连接数,若超过,则会抛异常。一方面,直接抛异常这个处理并不友好,Java 程序需要捕获异常并进行异常处理,另一方面,不能仅仅依靠服务端的最大连接数限制,而应该在数据库访问客户端的时候做到限流,数据库连接需要被有序可控地限制使用,多一种保护手段。

--> 限制连接(需要在客户端的 Java 程序中,实现业务线程排队获取数据库连接、限制最大并发线程数,起到限流、对后端数据库的保护作用。

使用连接池解决。

连接池:本质为一组 Java jar 包,介于应用程序和数据库 JDBC 的物理连接之间,负责帮助应用程序来管理 JDBC 连接,通过连接池暴露的接口,应用程序可以获取数据库连接。使用完毕以后,可以将数据库连接归还给连接池供下一个线程使用。连接池对 JDBC 连接进行有效管理,在连接池中的 JDBC 连接不足时,会自动创建连接;在空闲连接较多时,会自动销毁连接;在有多个线程获取数据库连接时,连接池提供了排队等待的功能,保证了应用程序有序的获得数据库连接。

 

连接池的使用:DBCP 连接池(使用最广的连接池)

DBCP:Apache 开源项目,tomcat 使用的连接池组件

包括三个 jar 包:commons-dbcp.jar  commons-pool.jar  commons-logging.jar

下载对应的 jar 包(缺一不可)https://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi

右键 project->Properties->Build Path->Libraries->add external jars->commons-dbcp2-2.1.1.jar

结构:

创建连接池对象:BasicDataSource 对象表示一个连接池

告诉 DBCP 如何连接到 JDBC:

.Url / .DriverName / .User / .Password

方法:

basicDataSource.getConnection():从连接池中租借一个连接以访问后端数据库

之后的操作同 JDBC,如 stmt = conn.createStatement(); rs = stmt.executeQuery(...); 等

conn.close():完成操作后,需将连接归还(在 JDBC 时为销毁连接 close(),而 DBCP 重写了 Connection 的 close 方法)

高级配置:

.setInitialSize();  // 第一次访问需要花费较多时间在创建连接等上。解决方法:应用程序启动时在连接池中预置一些连接,以保证第一次访问时已经有一定数量的连接。一般设置为预期业务平均访问量。

.setMaxTotal();// 当连接池中没有空闲连接,并有新的访问需要连接时,连接池会创建新的连接。但若此时连接数已经达到了 MaxTotal,则连接池不会新建连接,而是强制让该进程进入等待队列。setMaxTotal() 起到了限流保护数据库的作用。

.setMaxWaitMillis();  // 设置最大等待时间,当超过该时间,应用程序会得到一个 SQLException 异常

.setMaxIdle();  // 若连接池中的空闲连接数超过了 maxIdle 的数量,则会销毁一部分数据连接。减少不必要的资源损耗。

.setMinIdle();  // 反之,连接池中空闲连接数少于 minIdle 数量,则会创建一些连接,来保证连接池中有足够连接用于租借。一般为了避免不断进行销毁和创建连接,会将 maxIdle 和 minIdle 设置为一样的值。

// 定期检查功能。数据库服务端为了释放一些空闲等待的资源,默认自动关闭空闲时间超过一定数值的数据库连接(MySQL 默认关闭空闲超过 8 小时的数据库连接)。而服务器端关闭连接后,客户端并不知道连接已经被关闭了,当应用程序向连接池租借连接时,很可能将这些失效的连接租借出去从而出现 SQLException 异常。检查:当服务器端关闭连接之前,会将连接销毁掉,来保证应用程序租借的连接都是有效的。

.setTestWhileIdle(True);  // 开启上述功能

.setMinEvictableIdleTimeMillis();  // 销毁连接的最小空闲时间:当连接的空闲时间超过该值时,自动销毁该连接

.setTimeBetweenEvictionRunsMillis();  // 检查的间隔,建议小于服务器端自动关闭连接的时间

 

数据库连接池单元测试

本次得分为:80.00/80.00, 本次测试的提交时间为:2017-08-22, 如果你认为本次测试成绩不理想,你可以选择再做一次。
1单选 (5 分)

以下哪项不是使用数据库连接池带来的好处?

  • A. 连接池可以帮助用户屏蔽数据库服务端异常,使用连接池,应用程序不需要处理数据库异常。5.00/5.00
  • B. 当应用程序访问后端数据库的连接数超过设定的最大连接数限制时,连接池可以使得应用程序排队有序获取数据库连接。
  • C. 限制应用程序并发访问数据库的连接数,避免因为连接过多导致的数据库服务端资源耗尽,引发故障;
  • D. 避免每次访问数据库都新建立连接,节省连接的创建和销毁时间,提高应用程序访问后端数据库的性能。
2单选 (5 分)

有关 DBCP 的描述,下面哪项是不正确的?

  • A.DBCP 是 apache 基金会下面的一个开源数据库连接池项目。
  • B. 一个 DBCP 连接池可以创建多个不同数据库的连接。5.00/5.00
  • C.DBCP 底层依赖 JDBC 实现客户端到服务器端的数据库连接,所以即使使用 DBCP,也需要加载 MySQL JDBC 驱动架包。
  • D. 使用 DBCP 需要下载相应的 JAR 架包,加载到项目工程中。
3单选 (5 分)

以下哪个方法是 DBCP 将数据库连接归还给数据库连接池?

  • A..setMaxTotal()
  • B..close()5.00/5.00
  • C..setTestWhileIdle()
  • D..getConnection()
4单选 (5 分)

下列哪项是需要设置数据库连接定期检查的原因?

  • A. 避免应用程序第一次启动时,获取连接过慢。
  • B. 防止并发访问的数据库连接数过多,造成数据库服务端资源耗尽引发数据库宕机。
  • C. 避免数据库连接池中连接过少,造成应用程序访问数据库性能过差。
  • D. 防止服务端数据库连接异常关闭,导致连接池中的数据库连接失效。5.00/5.00
5多选 (40 分)

设置数据库连接定期检查,需要使用那几个方法?

  • A..setTestWhileIdle()13.33/40.00
  • B..setTimeBetweenEvictableRunMillis()13.33/40.00
  • C..setMaxTotal()
  • D..setMinEvictableIdleTimeMillis()13.33/40.00
6判断 (10 分)

为了避免连接池频繁的创建和销毁数据库连接,一般建议将 MaxIdle 和 MaxTotal 设置为一样。

  • A.×
  • B.√10.00/10.00
7判断 (10 分)

在设置数据库连接池 MaxTotal 参数时,应大于等于服务端 MySQ 设置的数据库最大连接数限制。

  • A.×10.00/10.00
  • B.√

 

数据库连接池作业

https://my.oschina.net/hava/blog/751954

需要同学们使用刚刚学习的数据库连接池相关知识,完成一道编程题目。

1(100 分)

请使用 DBCP 数据库连接池改造上节课程作业中完成的输出商品名称和库存的应用程序,通过连接池实现对数据库的访问。 

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.dbcp2.BasicDataSource;

public class ProductProcessingDBCPAssignment {

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> BasicDataSource bs = <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)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String DRIVER_NAME = "com.mysql.jdbc.Driver"<span style="color: rgba(0, 0, 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)">final</span> String DB_URL = "jdbc:mysql://localhost/productDB"<span style="color: rgba(0, 0, 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)">final</span> String DB_USER_NAME = "matt"<span style="color: rgba(0, 0, 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)">final</span> String DB_PASSWORD = "matt"<span style="color: rgba(0, 0, 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> dbcpInit() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ClassNotFoundException{
    bs </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BasicDataSource();
    bs.setUrl(DB_URL);
    bs.setDriverClassName(DRIVER_NAME);
    bs.setUsername(DB_USER_NAME);
    bs.setPassword(DB_PASSWORD);
}
 
</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)"> dataProcessing() {
    Connection conn </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    Statement stmt </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    ResultSet rs </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, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        conn </span>=<span style="color: rgba(0, 0, 0, 1)"> bs.getConnection();
        stmt </span>=<span style="color: rgba(0, 0, 0, 1)"> conn.createStatement();
        rs </span>= stmt.executeQuery("SELECT ProductName, Inventory FROM Product Where Id=1"<span style="color: rgba(0, 0, 0, 1)">);
        </span><span style="color: rgba(0, 0, 255, 1)">while</span><span style="color: rgba(0, 0, 0, 1)">(rs.next()) {
            System.out.println(rs.getString(</span>"ProductName") + ": " + rs.getString("Inventory"<span style="color: rgba(0, 0, 0, 1)">));
        }
    } </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)">finally</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)"> {
            </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)">) conn.close();
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (stmt != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) stmt.close();
            </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)">) 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)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> main(String[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> ClassNotFoundException {
    dbcpInit();
    dataProcessing();
}

}