本篇内容综合广大网友提供内容,笔者经过整理,对数据库连接池原理和实现过程做个很系统的并且通俗易懂的分析讲解,以及手写一个连接池实现过程作为演示。
一、早期通过JDBC方式操作数据库
我们先来看早期使用JDBC的方式操作数据库的过程,这里以mysql数据库为例讲解
JDBC操作数据库原理:一般来说,java应用程序访问数据库的过程是:
①装载数据库驱动程序;
②通过jdbc建立数据库连接;
③访问数据库,执行sql语句;
④断开数据库连接。
1、具体的代码实现步骤
创建数据库配置文件db.properties ,配置文件内容如下:
- #连接数据库的url,test表示数据库名,useUnicode=true表示使用Unicode字符集
- url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8;
- #用户名
- user=root
- #密码
- password=root
- #MySQL数据库加载驱动
- driverClass=com.mysql.jdbc.Driver
定义一个使用jdbc连接数据库的工具类JdbcUtil.java
- public class JdbcUtil{
- //定义全局变量
- private static String url = null;
- private static String user = null;
- private static String password = null;
- private static driverClass = null;
- //读取配置文件内容,放在静态代码块中就行,因为只需要加载一次就可以了
- static{
- try{
- Properties props = new Properties();
- //使用类路径加载的方式读取配置文件
- //读取的文件路径要以“/”开头,使用"/"开头会直接定位到工程的src路径下
- InputStream in = JdbcUtil.class.getResourceAsStream("/db.properties");
- //加载配置文件
- props.load(in);
- //读取配置文件信息
- url = props.getProperty("url");
- user = props.getProperty("user");
- password = props.getProperty("password");
- driverClass = props.getProperty("driverClass");
- //动态注册mysql驱动程序
- Class.forName(driverClass);
- System.out.println("成功加载MySQL驱动程序");
- }catch(Exception e){
- e.printStackTrace();
- System.out.println("mysql驱动程序注册失败!!!");
- }
- }
- //获取连接对象Connection
- public static Connection getConnection(){
- try{
- //要连接数据库,需要向java.sql.DriverManager请求并获得Connection对象
- return DriverManager.getConnection(url,user,password);
- }catch(SQLException e){
- e.printStackTrace();
- //抛出运行时异常
- throw new RuntimeException();
- }
- }
- //关闭连接的方法,后打开的先关闭
- public static void close(Connection conn,Statement stmt,ResultSet rs){
- //关闭ResultSet对象
- if(rs != null){
- try{
- //关闭rs,设置rs=null,因为java会优先回收值为null的变量
- rs.close();
- rs = null;
- }catch(SQLException e){
- e.printStackTrace();
- throw new RuntimeException();
- }
- }
- //关闭Statement对象,因为PrepareStatement和CallableStatement都是Statement的子接口,所以这里只需要有关闭Statement对象的方法就可以了
- if(stmt != null){
- try{
- stmt.close();
- stmt = null;
- }catch(SQLException e){
- e.printStackTrace();
- throw new RuntimeException();
- }
- }
- //关闭Connection对象
- if(conn != null){
- try{
- conn.close();
- conn = null;
- }catch(SQLException e){
- e.printStackTrace();
- throw new RuntimeException();
- }
- }
- }
- }
到此,这是一个完整的使用JDBC方式操作数据库的过程,下面我们新建一个测试类TestConn.java 测试一下
- package demo;
- import java.sql.Connection;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Statement;
- import utils.JdbcUtil;
- public class TestConn {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Connection conn = JdbcUtil.getConnection();
- try {
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT * FROM user") ;
- //使用结果集(ResultSet)对象的访问方法获取数据:
- while(rs.next()){
- String name = rs.getString("name") ;
- System.out.println(name);
- String pass = rs.getString(1);//此方法比较高效,列是从左到右编号的,并且从列1开始
- System.out.println(pass);
- }
- //操作完成以后关闭JDBC对象,要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声明顺序相反:
- //关闭顺序1、关闭记录集,2、关闭声明,3、关闭连接对象
- JdbcUtil.close(conn, stmt, rs);
- } catch (SQLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
2、分析
程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
上述的用户查询案例,如果同时有1000人访问,就会不断的有数据库连接、断开操作:
通过上面的分析,我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。原理如下:
二、技术演进出来的数据库连接池
由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。
我们自己尝试开发一个连接池,来为上面的查询业务提供数据库连接服务:
① 编写class 实现DataSource 接口
② 在class构造器一次性创建10个连接,将连接保存LinkedList中
③ 实现getConnection 从 LinkedList中返回一个连接
④ 提供将连接放回连接池中方法
1、实现mysql数据库连接池代码
- public class MyDataSource implements DataSource {
- //链表实现栈结构
- privateLinkedList<Connection> dataSources = new LinkedList<Connection>();
- //无参构造器,初始化连接数量
- public MyDataSource() {
- //一次性创建10个连接
- for(int i = 0; i < 10; i++) {
- try {
- //1、获取数据库连接对象
- Connection conn = JdbcUtil.getConnection();
- //2、将连接加入连接池中
- dataSources.add(con);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- @Override
- public Connection getConnection() throws SQLException {
- //取出连接池中一个连接
- final Connection conn = dataSources.removeFirst(); //删除第一个连接返回
- return conn;
- }
- //将连接放回连接池
- public void releaseConnection(Connection conn) {
- dataSources.add(conn);
- }
- }
这就是数据库连接池的原理,它大大提供了数据库连接的利用率,减小了内存吞吐的开销。我们在开发过程中,我们只关注我们的业务代码的开发,就不需要再关心数据库连接的问题,自然有数据库连接池帮助我们处理。