03.数据库开发 - 第2章.数据库连接池

数据库连接池

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

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

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

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

  1. 客户端发送给服务器TCP请求
  2. 服务器端随机生成密码种子返回客户端
  3. 客户端利用密码种子和自己保存的数据库密码按照约定的加密算法得到加密密码,将其发送给服务器进行验证
  4. 服务器端验证完成,建立链接成功。

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

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

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

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

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

 

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

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

           使用连接池解决。

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

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

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

                     包括3个jar包,commons-dbcp.jar commons-pool.jar commons-logging.jar

                     下载对应的jar包(缺一不可)

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

           结构:

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

                                告诉DBCP如何连接到JDBC

                                          .Url / .DriverName /.User /.Password

           方法:

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

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

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

           高级配置:

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

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

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

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

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

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

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

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

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

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

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

import org.apache.commons.dbcp2.BasicDataSource;

public class HelloJDBC5 {

	public static BasicDataSource bs = null;

	static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
	static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
	static final String USER = "root";
	static final String PASSWORD = "xjj5211314";

	private static void dbpoolInit() {
		bs = new BasicDataSource();
		bs.setUrl(DB_URL);
		bs.setDriverClassName(JDBC_DRIVER);
		bs.setUsername(USER);
		bs.setPassword(PASSWORD);
	}

	private static void dbPoolTest(int i) {
		Connection conn = null;
		PreparedStatement pmpt = null;
		ResultSet rs = null;

		try {
			conn = bs.getConnection();
			String sql = "select ProductName,Inventory from Product where id = ?";
			pmpt = conn.prepareStatement(sql);
			pmpt.setInt(1, i);
			rs = pmpt.executeQuery();
			while (rs.next()) {
				System.out.println(rs.getString("ProductName") + ":" + rs.getString("Inventory"));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			// 4. 清理环境
			try {
				if (conn != null) {
					conn.close();
				}
				if (pmpt != null) {
					pmpt.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}

	}

	public static void main(String[] args) throws ClassNotFoundException {
		dbpoolInit();
		dbPoolTest(2);
		System.out.println("ok");
	}
}

猜你喜欢

转载自blog.csdn.net/liujingjie2010/article/details/82961072