一、为什么在连接数据库时要使用连接池
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。 连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
二、数据库连接池的基本原理
数据库连接池的基本思想就是为数据库连接 建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定 连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量?使用情况,为系统开发?测试及性能调 整提供依据。
三、数据库连接池的工作原理
连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭。
第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。
四、连接池关键问题分析
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为Java语言自身提供了对并发管理的支 持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:
public synchronized Connection getConnection()
2、多数据库服务器和多用户
对于大型的企业级应用,常常需要同时连接不同的数据库(如连接Oracle和Sybase)。如何连接不同的数据库呢?我们采用的策略是:设计 一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的url地址()?用户名()?密 码()等信息。如 tx.url=172.21.15.123:5000/tx_it,tx.user=yang,tx.password=yang321。根据资源文件提 供的信息,创建多个连接池类的实例,每一个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连 接池。
对于同一个数据库有多个用户使用不同的名称和密码访问的情况,也可以通过资源文件处理,即在资源文件中设置多个具有相同url地址,但具有不同用户名和密码的数据库连接信息。
3、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-ALL-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
在Java语言中,Connection类本身提供了对事务的支持,可以通过设置Connection的AutoCommit属性为 false,然后显式的调用commit或rollback方法来实现。但要高效的进行Connection复用,就必须提供相应的事务支持机制。可采用 每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。
4、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内 有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用 就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxConn),如果没有 达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果 等待时间超过预定时间timeout,则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状 态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。
5、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConn)和最大连接数(maxConn)来控制连接 池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很 快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连 接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
五、连接池实现代码(java)
package book.util; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.Date; import java.sql.Driver; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Vector; public class Pool { public static void main(String[] args) { Pool pool = new Pool("com.microsoft.sqlserver.jdbc.SQLServerDriver","jdbc:sqlserver://localhost:1433;DataBaseName=Book","sa","aaaaaa"); try { pool.createConnections(4); } catch (SQLException e) { e.printStackTrace(); } Connection conn = pool.getConnection(); try { String sql = "select * from allbook"; PreparedStatement ps; ps = conn.prepareStatement(sql); ResultSet rs=ps.executeQuery(); while(rs.next()){ System.out.println(rs.getString("BOOKNAME")); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ pool.returnConnection(conn); } //long startTime=System.currentTimeMillis(); //long endTime=System.currentTimeMillis(); //System.out.println("程序运行时间: "+(endTime-startTime)+"ms"); } private String jdbcDriver = "";//数据库驱动 private String dbUrl = "";//数据库url private String dbUsername = "";//数据库用户名 private String dbPassword = "";//数据库密码 private String testTable = ""; private int initialConnectionsNum = 10;//连接池初始连接数 private int maxConnectionsNum = 50;//连接池最大连接数 private int incrementalConnections = 5;//每次动态添加的连接数 private Vector<PooledConnection> connections = null;//向量,存放连接池中的连接,初始为空 /*无参构造函数*/ public Pool() {} /*带参数的构造函数 * 初始化数据库驱动、数据库url、数据库用户名、数据库密码、测试表 * */ public Pool(String driver, String url, String name, String pass) { this.jdbcDriver = driver; this.dbUrl = url; this.dbUsername = name; this.dbPassword = pass; //this.testTable = table; try { this.createPool(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /*函数,创建连接池*/ public synchronized void createPool() throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException { /*确保连接池为创建,如果已经创建,则保存连接的向量不为空 * */ if (this.connections != null) { return ; } //驱动器实例化 Driver driver = (Driver)(Class.forName(this.jdbcDriver).newInstance()); //注册驱动器 DriverManager.registerDriver(driver); //创建保存连接的向量 this.connections = new Vector<PooledConnection>(); //创建数据库连接 this.createConnections(this.initialConnectionsNum); } /*函数,创建数据库连接 * */ private void createConnections (int num) throws SQLException { /*循环创建连接 * 需要首先检查当前连接数是否已经超出连接池最大连接数 * */ for (int i = 0; i < num; ++i) { //检查 if (this.connections.size() >= this.maxConnectionsNum) { return; } //创建连接 this.connections.addElement (new PooledConnection(newConnection())); } } /*函数,创建一个数据库连接*/ private Connection newConnection() throws SQLException { /*创建连接*/ Connection con = DriverManager.getConnection(this.dbUrl, this.dbUsername, this.dbPassword); /*如果是第一次创建连接,则检查所连接的数据库的允许最大连接数是否小于 * 我们所设定的最大连接数*/ if (this.connections.size() == 0) { DatabaseMetaData metadata = con.getMetaData(); //得到数据库最大连接数 int dbMaxConnectionsNum = metadata.getMaxConnections(); //如果数据库最大连接数更小,则更改我们所设定的连接池最大连接数 if (dbMaxConnectionsNum > 0 && this.maxConnectionsNum > dbMaxConnectionsNum) { this.maxConnectionsNum = dbMaxConnectionsNum; } } return con; } /*函数,得到一个可用连接 * */ public synchronized Connection getConnection () { Connection con = null; /*检查连接池是否已经建立*/ if (this.connections == null) { return con; } //得到一个可用连接 try { con = this.getFreeConnection(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } //如果未找到合适连接,循环等待、查找,知道找到合适连接 while(con == null) { this.wait(30); try { con = this.getFreeConnection(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return con; } /*函数,得到一个可用连接*/ private Connection getFreeConnection() throws SQLException { Connection con = null; //查找一个可用连接 con = this.findFreeConnection(); //如果未找到可用连接,就建立一些新的连接,再次查找 if (con == null) { this.createConnections(this.incrementalConnections); //再次查找 con = this.findFreeConnection(); } return con; } /*函数,从现有连接中查找一个可用连接 * 在现有的连接中(向量connections中)找到一个空闲连接, * 并测试这个链接是否可用,若不可用则重新建立连接,替换原来的连接*/ private Connection findFreeConnection () throws SQLException { Connection con = null; for (int i = 0; i < this.connections.size(); ++i) { PooledConnection pol = (PooledConnection)this.connections.get(i); if (!pol.isBusy()) { /*如果此链接未被使用,则返回这个连接并,设置正在使用标志*/ con = pol.getCon(); pol.setBusy(true); /*测试连接是否可用*/ if (!this.testCon(con)) { con = this.newConnection(); pol.setCon(con); } break; } } return con; } /*函数,测试连接是否可用 * */ private boolean testCon (Connection con) { boolean useable = true; try { Statement st = con.createStatement(); ResultSet rs = st.executeQuery("select count(*) from " + this.testTable); rs.next(); } catch(SQLException e) { /*上面抛出异常,连接不可用,关闭*/ useable = false; this.closeConnection(con); } return useable; } /*函数,将使用完毕的连接放回连接池中 * */ public void returnConnection(Connection con) { /*确保连接池存在*/ if (this.connections == null) { return ; } for (int i = 0; i < this.connections.size(); ++i) { PooledConnection pool = this.connections.get(i); //找到相应连接,设置正在使用标志为false if (con == pool.getCon()) { pool.setBusy(false); } } } /*函数,刷新连接池中的连接*/ public synchronized void refreshConneciontPool () throws SQLException { /*确保连接池存在*/ if (this.connections == null) { return ; } for (int i = 0; i < this.connections.size(); ++i) { PooledConnection pool = this.connections.get(i); if (pool.isBusy()) { this.wait(5000); } this.closeConnection(pool.getCon()); pool.setCon(this.newConnection()); pool.setBusy(false); } } /*函数,关闭连接池*/ public void closeConnectionPool() { /*确保连接池存在*/ if (this.connections == null) { return ; } for (int i = 0; i < this.connections.size(); ++i) { PooledConnection pool = this.connections.get(i); if (pool.isBusy()) { this.wait(5000); } this.closeConnection(pool.getCon()); this.connections.remove(i); } this.connections = null; } /*函数,暂时无可用连接,进入等待队列等待m秒,再试 * */ private void wait(int mSecond) { try { Thread.sleep(mSecond); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * @return the jdbcDriver */ public String getJdbcDriver() { return jdbcDriver; } /** * @param jdbcDriver the jdbcDriver to set */ public void setJdbcDriver(String jdbcDriver) { this.jdbcDriver = jdbcDriver; } /** * @return the dbUrl */ public String getDbUrl() { return dbUrl; } /** * @param dbUrl the dbUrl to set */ public void setDbUrl(String dbUrl) { this.dbUrl = dbUrl; } /** * @return the dbUsername */ public String getDbUsername() { return dbUsername; } /** * @param dbUsername the dbUsername to set */ public void setDbUsername(String dbUsername) { this.dbUsername = dbUsername; } /** * @return the dbPassword */ public String getDbPassword() { return dbPassword; } /** * @param dbPassword the dbPassword to set */ public void setDbPassword(String dbPassword) { this.dbPassword = dbPassword; } /** * @return the testTable */ public String getTestTable() { return testTable; } /** * @param testTable the testTable to set */ public void setTestTable(String testTable) { this.testTable = testTable; } /** * @return the initialConnectionsNum */ public int getInitialConnectionsNum() { return initialConnectionsNum; } /** * @param initialConnectionsNum the initialConnectionsNum to set */ public void setInitialConnectionsNum(int initialConnectionsNum) { this.initialConnectionsNum = initialConnectionsNum; } /** * @return the maxConnectionsNum */ public int getMaxConnectionsNum() { return maxConnectionsNum; } /** * @param maxConnectionsNum the maxConnectionsNum to set */ public void setMaxConnectionsNum(int maxConnectionsNum) { this.maxConnectionsNum = maxConnectionsNum; } /** * @return the incrementalConnections */ public int getIncrementalConnections() { return incrementalConnections; } /** * @param incrementalConnections the incrementalConnections to set */ public void setIncrementalConnections(int incrementalConnections) { this.incrementalConnections = incrementalConnections; } /** * @return the connections */ public Vector<PooledConnection> getConnections() { return connections; } /** * @param connections the connections to set */ public void setConnections(Vector<PooledConnection> connections) { this.connections = connections; } /*函数,连接使用完毕,关闭连接*/ private void closeConnection (Connection con) { try { con.close(); } catch(SQLException e) { e.printStackTrace(); } } /*内部使用的保存数据库连接的类 * 两个成员变量:连接、是否正在使用*/ class PooledConnection { private Connection con = null;//连接 private boolean busy = false;//是否正在使用,默认为非 /*构造函数*/ public PooledConnection(Connection con) { this.con = con; } /** * @return the con */ public Connection getCon() { return con; } /** * @param con the con to set */ public void setCon(Connection con) { this.con = con; } /** * @return the busy */ public boolean isBusy() { return busy; } /** * @param busy the busy to set */ public void setBusy(boolean busy) { this.busy = busy; } } }