MySQL 数据库连接池
数据库连接池(connection pool)的工作原理
资源池(Resource Pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接 建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定 连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调 整提供依据。连接池的基本工作原理见下图2。
实例:
1 import java.sql.Connection; 2 import java.sql.DatabaseMetaData; 3 import java.sql.Driver; 4 import java.sql.DriverManager; 5 import java.sql.SQLException; 6 import java.sql.Statement; 7 import java.util.Enumeration; 8 import java.util.Vector; 9 10 public class ConnectionPool { 11 private String jdbcDriver = ""; // 数据库驱动 12 private String dbUrl = ""; // 数据 URL 13 private String dbUsername = ""; // 数据库用户名 14 private String dbPassword = ""; // 数据库用户密码 15 private String testTable = ""; // 测试连接是否可用的测试表名,默认没有测试表
16 private int initialConnections = 10; // 连接池的初始大小 17 private int incrementalConnections = 5;// 连接池自动增加的大小 18 private int maxConnections = 50; // 连接池最大的大小 19 private Vector connections = null; // 存放连接池中数据库连接的向量 , 初始时为 null 20 // 它中存放的对象为 PooledConnection 型 21 22 /** 23 * 构造函数 24 * 25 * @param jdbcDriver 26 * String JDBC 驱动类串 27 * @param dbUrl 28 * String 数据库 URL 29 * @param dbUsername 30 * String 连接数据库用户名 31 * @param dbPassword 32 * String 连接数据库用户的密码 33 * 34 */ 35 public ConnectionPool(String jdbcDriver, String dbUrl, String dbUsername, 36 String dbPassword) { 37 this.jdbcDriver = jdbcDriver; 38 this.dbUrl = dbUrl; 39 this.dbUsername = dbUsername; 40 this.dbPassword = dbPassword; 41 } 42 43 /** 44 * 返回连接池的初始大小 45 * 46 * @return 初始连接池中可获得的连接数量 47 */ 48 public int getInitialConnections() { 49 return this.initialConnections; 50 } 51 52 /** 53 * 设置连接池的初始大小 54 * 55 * @param 用于设置初始连接池中连接的数量 56 */ 57 58 public void setInitialConnections(int initialConnections) { 59 this.initialConnections = initialConnections; 60 } 61 62 /** 63 * 返回连接池自动增加的大小 、 64 * 65 * @return 连接池自动增加的大小 66 */ 67 public int getIncrementalConnections() { 68 return this.incrementalConnections; 69 } 70 71 /** 72 * 设置连接池自动增加的大小 73 * 74 * @param 连接池自动增加的大小 75 */ 76 77 public void setIncrementalConnections(int incrementalConnections) { 78 this.incrementalConnections = incrementalConnections; 79 } 80 81 /** 82 * 返回连接池中最大的可用连接数量 83 * 84 * @return 连接池中最大的可用连接数量 85 */ 86 87 public int getMaxConnections() { 88 return this.maxConnections; 89 } 90 91 /** 92 * 设置连接池中最大可用的连接数量 93 * 94 * @param 设置连接池中最大可用的连接数量值 95 */ 96 97 public void setMaxConnections(int maxConnections) { 98 this.maxConnections = maxConnections; 99 } 100 101 /** 102 * 获取测试数据库表的名字 103 * 104 * @return 测试数据库表的名字 105 */ 106 107 public String getTestTable() { 108 return this.testTable; 109 } 110 111 /** 112 * 设置测试表的名字 113 * 114 * @param testTable 115 * String 测试表的名字 116 */ 117 118 public void setTestTable(String testTable) { 119 this.testTable = testTable; 120 } 121 122 /** 123 * 124 * 创建一个数据库连接池,连接池中的可用连接的数量采用类成员 initialConnections 中设置的值 125 */ 126 127 public synchronized void createPool() throws Exception { 128 // 确保连接池没有创建 129 // 如果连接池己经创建了,保存连接的向量 connections 不会为空 130 if (connections != null) { 131 return; // 如果己经创建,则返回 132 } 133 // 实例化 JDBC Driver 中指定的驱动类实例 134 Driver driver = (Driver) (Class.forName(this.jdbcDriver).newInstance()); 135 DriverManager.registerDriver(driver); // 注册 JDBC 驱动程序 136 // 创建保存连接的向量 , 初始时有 0 个元素 137 connections = new Vector(); 138 // 根据 initialConnections 中设置的值,创建连接。 139 createConnections(this.initialConnections); 140 // System.out.println(" 数据库连接池创建成功! "); 141 } 142 143 /** 144 * 创建由 numConnections 指定数目的数据库连接 , 并把这些连接 放入 connections 向量中 145 * 146 * @param numConnections 147 * 要创建的数据库连接的数目 148 */ 149 150 @SuppressWarnings("unchecked") 151 private void createConnections(int numConnections) throws SQLException { 152 // 循环创建指定数目的数据库连接 153 for (int x = 0; x < numConnections; x++) { 154 // 是否连接池中的数据库连接的数量己经达到最大?最大值由类成员 maxConnections 155 // 指出,如果 maxConnections 为 0 或负数,表示连接数量没有限制。 156 // 如果连接数己经达到最大,即退出。 157 if (this.maxConnections > 0 158 && this.connections.size() >= this.maxConnections) { 159 break; 160 } 161 // add a new PooledConnection object to connections vector 162 // 增加一个连接到连接池中(向量 connections 中) 163 try { 164 connections.addElement(new PooledConnection(newConnection())); 165 } catch (SQLException e) { 166 System.out.println(" 创建数据库连接失败! " + e.getMessage()); 167 throw new SQLException(); 168 } 169 // System.out.println(" 数据库连接己创建 ......"); 170 } 171 } 172 173 /** 174 * 创建一个新的数据库连接并返回它 175 * 176 * @return 返回一个新创建的数据库连接 177 */ 178 179 private Connection newConnection() throws SQLException { 180 // 创建一个数据库连接 181 Connection conn = DriverManager.getConnection(dbUrl, dbUsername, 182 dbPassword); 183 // 如果这是第一次创建数据库连接,即检查数据库,获得此数据库允许支持的 184 // 最大客户连接数目 185 // connections.size()==0 表示目前没有连接己被创建 186 if (connections.size() == 0) { 187 DatabaseMetaData metaData = conn.getMetaData(); 188 int driverMaxConnections = metaData.getMaxConnections(); 189 // 数据库返回的 driverMaxConnections 若为 0 ,表示此数据库没有最大 190 // 连接限制,或数据库的最大连接限制不知道 191 // driverMaxConnections 为返回的一个整数,表示此数 据库允许客户连接的数目 192 // 如果连接池中设置的最大连接数量大于数据库允许的连接数目 , 则置连接池的最大 193 // 连接数目为数据库允许的最大数目 194 if (driverMaxConnections > 0 195 && this.maxConnections > driverMaxConnections) { 196 this.maxConnections = driverMaxConnections; 197 } 198 } 199 return conn; // 返回创建的新的数据库连接 200 } 201 202 /** 203 * 通过调用 getFreeConnection() 函数返回一个可用的数据库连接 , 如果当前没有可用的数据库连接,并且更多的数据库连接不能创 204 * 建(如连接池大小的限制),此函数等待一会再尝试获取。 205 * 206 * @return 返回一个可用的数据库连接对象 207 */ 208 209 public synchronized Connection getConnection() throws SQLException { 210 // 确保连接池己被创建 211 if (connections == null) { 212 return null; // 连接池还没创建,则返回 null 213 } 214 Connection conn = getFreeConnection(); // 获得一个可用的数据库连接 215 // 如果目前没有可以使用的连接,即所有的连接都在使用中 216 while (conn == null) { 217 // 等一会再试 218 // System.out.println("Wait"); 219 wait(250); 220 conn = getFreeConnection(); // 重新再试,直到获得可用的连接,如果 221 // getFreeConnection() 返回的为 null 222 // 则表明创建一批连接后也不可获得可用连接 223 } 224 return conn;// 返回获得的可用的连接 225 } 226 227 /** 228 * 本函数从连接池向量 connections 中返回一个可用的的数据库连接,如果 当前没有可用的数据库连接,本函数则根据 229 * incrementalConnections 设置 的值创建几个数据库连接,并放入连接池中。 如果创建后,所有的连接仍都在使用中,则返回 null 230 * 231 * @return 返回一个可用的数据库连接 232 */ 233 private Connection getFreeConnection() throws SQLException { 234 // 从连接池中获得一个可用的数据库连接 235 Connection conn = findFreeConnection(); 236 if (conn == null) { 237 // 如果目前连接池中没有可用的连接 238 // 创建一些连接 239 createConnections(incrementalConnections); 240 // 重新从池中查找是否有可用连接 241 conn = findFreeConnection(); 242 if (conn == null) { 243 // 如果创建连接后仍获得不到可用的连接,则返回 null 244 return null; 245 } 246 } 247 return conn; 248 } 249 250 /** 251 * 查找连接池中所有的连接,查找一个可用的数据库连接, 如果没有可用的连接,返回 null 252 * 253 * @return 返回一个可用的数据库连接 254 */ 255 256 private Connection findFreeConnection() throws SQLException { 257 Connection conn = null; 258 PooledConnection pConn = null; 259 // 获得连接池向量中所有的对象 260 Enumeration enumerate = connections.elements(); 261 // 遍历所有的对象,看是否有可用的连接 262 while (enumerate.hasMoreElements()) { 263 pConn = (PooledConnection) enumerate.nextElement(); 264 if (!pConn.isBusy()) { 265 // 如果此对象不忙,则获得它的数据库连接并把它设为忙 266 conn = pConn.getConnection(); 267 pConn.setBusy(true); 268 // 测试此连接是否可用 269 if (!testConnection(conn)) { 270 // 如果此连接不可再用了,则创建一个新的连接, 271 // 并替换此不可用的连接对象,如果创建失败,返回 null 272 try { 273 conn = newConnection(); 274 } catch (SQLException e) { 275 System.out.println(" 创建数据库连接失败! " + e.getMessage()); 276 return null; 277 } 278 pConn.setConnection(conn); 279 } 280 break; // 己经找到一个可用的连接,退出 281 } 282 } 283 return conn;// 返回找到到的可用连接 284 } 285 286 /** 287 * 测试一个连接是否可用,如果不可用,关掉它并返回 false 否则可用返回 true 288 * 289 * @param conn 290 * 需要测试的数据库连接 291 * @return 返回 true 表示此连接可用, false 表示不可用 292 */ 293 294 private boolean testConnection(Connection conn) { 295 try { 296 // 判断测试表是否存在 297 if (testTable.equals("")) { 298 // 如果测试表为空,试着使用此连接的 setAutoCommit() 方法 299 // 来判断连接否可用(此方法只在部分数据库可用,如果不可用 , 300 // 抛出异常)。注意:使用测试表的方法更可靠 301 conn.setAutoCommit(true); 302 } else {// 有测试表的时候使用测试表测试 303 // check if this connection is valid 304 Statement stmt = conn.createStatement(); 305 stmt.execute("select count(*) from " + testTable); 306 } 307 } catch (SQLException e) { 308 // 上面抛出异常,此连接己不可用,关闭它,并返回 false; 309 closeConnection(conn); 310 return false; 311 } 312 // 连接可用,返回 true 313 return true; 314 } 315 316 /** 317 * 此函数返回一个数据库连接到连接池中,并把此连接置为空闲。 所有使用连接池获得的数据库连接均应在不使用此连接时返回它。 318 * 319 * @param 需返回到连接池中的连接对象 320 */ 321 322 public void returnConnection(Connection conn) { 323 // 确保连接池存在,如果连接没有创建(不存在),直接返回 324 if (connections == null) { 325 System.out.println(" 连接池不存在,无法返回此连接到连接池中 !"); 326 return; 327 } 328 PooledConnection pConn = null; 329 Enumeration enumerate = connections.elements(); 330 // 遍历连接池中的所有连接,找到这个要返回的连接对象 331 while (enumerate.hasMoreElements()) { 332 pConn = (PooledConnection) enumerate.nextElement(); 333 // 先找到连接池中的要返回的连接对象 334 if (conn == pConn.getConnection()) { 335 // 找到了 , 设置此连接为空闲状态 336 pConn.setBusy(false); 337 break; 338 } 339 } 340 } 341 342 /** 343 * 刷新连接池中所有的连接对象 344 * 345 */ 346 347 public synchronized void refreshConnections() throws SQLException { 348 // 确保连接池己创新存在 349 if (connections == null) { 350 System.out.println(" 连接池不存在,无法刷新 !"); 351 return; 352 } 353 PooledConnection pConn = null; 354 Enumeration enumerate = connections.elements(); 355 while (enumerate.hasMoreElements()) { 356 // 获得一个连接对象 357 pConn = (PooledConnection) enumerate.nextElement(); 358 // 如果对象忙则等 5 秒 ,5 秒后直接刷新 359 if (pConn.isBusy()) { 360 wait(5000); // 等 5 秒 361 } 362 // 关闭此连接,用一个新的连接代替它。 363 closeConnection(pConn.getConnection()); 364 pConn.setConnection(newConnection()); 365 pConn.setBusy(false); 366 } 367 } 368 369 /** 370 * 关闭连接池中所有的连接,并清空连接池。 371 */ 372 373 public synchronized void closeConnectionPool() throws SQLException { 374 // 确保连接池存在,如果不存在,返回 375 if (connections == null) { 376 System.out.println(" 连接池不存在,无法关闭 !"); 377 return; 378 } 379 PooledConnection pConn = null; 380 Enumeration enumerate = connections.elements(); 381 while (enumerate.hasMoreElements()) { 382 pConn = (PooledConnection) enumerate.nextElement(); 383 // 如果忙,等 5 秒 384 if (pConn.isBusy()) { 385 wait(5000); // 等 5 秒 386 } 387 // 5 秒后直接关闭它 388 closeConnection(pConn.getConnection()); 389 // 从连接池向量中删除它 390 connections.removeElement(pConn); 391 } 392 // 置连接池为空 393 connections = null; 394 } 395 396 /** 397 * 关闭一个数据库连接 398 * 399 * @param 需要关闭的数据库连接 400 */ 401 402 private void closeConnection(Connection conn) { 403 try { 404 conn.close(); 405 } catch (SQLException e) { 406 System.out.println(" 关闭数据库连接出错: " + e.getMessage()); 407 } 408 } 409 410 /** 411 * 使程序等待给定的毫秒数 412 * 413 * @param 给定的毫秒数 414 */ 415 416 private void wait(int mSeconds) { 417 try { 418 Thread.sleep(mSeconds); 419 } catch (InterruptedException e) { 420 } 421 } 422 423 /** 424 * 425 * 内部使用的用于保存连接池中连接对象的类 此类中有两个成员,一个是数据库的连接,另一个是指示此连接是否 正在使用的标志。 426 */ 427 428 class PooledConnection { 429 Connection connection = null;// 数据库连接 430 boolean busy = false; // 此连接是否正在使用的标志,默认没有正在使用 431 432 // 构造函数,根据一个 Connection 构告一个 PooledConnection 对象 433 public PooledConnection(Connection connection) { 434 this.connection = connection; 435 } 436 437 // 返回此对象中的连接 438 public Connection getConnection() { 439 return connection; 440 } 441 442 // 设置此对象的,连接 443 public void setConnection(Connection connection) { 444 this.connection = connection; 445 } 446 447 // 获得对象连接是否忙 448 public boolean isBusy() { 449 return busy; 450 } 451 452 // 设置对象的连接正在忙 453 public void setBusy(boolean busy) { 454 this.busy = busy; 455 } 456 } 457 458 }
测试类:
1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.ResultSet; 4 import java.sql.SQLException; 5 import java.sql.Statement; 6 7 public class ConnecttionPoolTest { 8 /** 9 * @param args 10 * @throws Exception 11 */ 12 public static void main(String[] args) throws Exception { 13 // TODO Auto-generated method stub 14 try { 15 // 创建数据库连接库对象 16 ConnectionPool connPool = new ConnectionPool( 17 "com.mysql.jdbc.Driver", 18 "jdbc:mysql://localhost:3306/test", "root", "123456"); 19 // 新建数据库连接库 20 connPool.createPool(); 21 // SQL测试语句 22 String sql = "Select * from pet"; 23 // 设定程序运行起始时间 24 long start = System.currentTimeMillis(); 25 // 循环测试100次数据库连接 26 for (int i = 0; i < 100; i++) { 27 Connection conn = connPool.getConnection(); // 从连接库中获取一个可用的连接 28 Statement stmt = conn.createStatement(); 29 ResultSet rs = stmt.executeQuery(sql); 30 while (rs.next()) { 31 String name = rs.getString("name"); 32 // System.out.println("查询结果" + name); 33 } 34 rs.close(); 35 stmt.close(); 36 connPool.returnConnection(conn);// 连接使用完后释放连接到连接池 37 } 38 System.out.println("经过100次的循环调用,使用连接池花费的时间:" 39 + (System.currentTimeMillis() - start) + "ms"); 40 // connPool.refreshConnections();//刷新数据库连接池中所有连接,即不管连接是否正在运行,都把所有连接都释放并放回到连接池。注意:这个耗时比较大。 41 connPool.closeConnectionPool();// 关闭数据库连接池。注意:这个耗时比较大。 42 // 设定程序运行起始时间 43 start = System.currentTimeMillis(); 44 // 导入驱动 45 Class.forName("com.mysql.jdbc.Driver"); 46 for (int i = 0; i < 100; i++) { 47 // 创建连接 48 Connection conn = DriverManager.getConnection( 49 "jdbc:mysql://localhost:3306/test", "root", "123456"); 50 Statement stmt = conn.createStatement(); 51 ResultSet rs = stmt.executeQuery(sql); 52 while (rs.next()) { 53 } 54 rs.close(); 55 stmt.close(); 56 conn.close();// 关闭连接 57 } 58 System.out.println("经过100次的循环调用,不使用连接池花费的时间:" 59 + (System.currentTimeMillis() - start) + "ms"); 60 } catch (SQLException e) { 61 e.printStackTrace(); 62 } catch (ClassNotFoundException e) { 63 e.printStackTrace(); 64 } 65 } 66 }