一、为什么在连接数据库时要使用连接池
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均相应一个物理数据库连接。每次操作都打开一个物理连接,使用完都关闭连接。这样造成系统的 性能低下。 数据库连接池的解决方式是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象)。由应用程序动态地对池中的连接进行申请、使用和释放。
对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。而且应用程序能够依据池中连接的使用率,动态添加或降低池中的连接数。 连接池技术尽可能多地重用了消耗内存地资源。大大节省了内存。提高了server地服务效率,能够支持很多其它的客户服务。通过使用连接池,将大大提高程序执行效率,同一时候。我们能够通过其自身的管理机制来监视数据库连接的数量、使用情况等。
二、数据库连接池的基本原理
数据库连接池的基本思想就是为数据库连接 建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当须要建立数据库连接时,仅仅需从“缓冲池”中取出一个,使用完成之后再放回去。我们能够通过设定 连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们能够通过连接池的管理机制监视数据库的连接的数量?
使用情况,为系统开发?測试及性能调 整提供根据。
三、数据库连接池的工作原理
连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭。
第一、连接池的建立。一般在系统初始化时,连接池会依据系统配置建立,并在池中创建了几个连接对象。以便使用时能从连接池中获取。
连接池中的连接不能任意创建和关闭。这样避免了连接任意建立和关闭造成的系统开销。Java中提供了非常多容器类能够方便的构建连接池,比如Vector、Stack等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有非常大的影响。其管理策略是:
当客户请求数据库连接时。首先查看连接池中是否有空暇连接,假设存在空暇连接,则将连接分配给客户使用。假设没有空暇连接,则查看当前所开的连接数是否已经达到最大连接数。假设没达到就又一次创建一个连接给请求的客户。假设达到就按设定的最大等待时间进行等待,假设超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时。先推断该连接的引用次数是否超过了规定值,假设超过就从连接池中删除该连接,否则保留为其它客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
第三、连接池的关闭。当应用程序退出时。关闭连接池中全部的连接。释放连接池相关的资源,该过程正好与创建相反。
四、连接池关键问题分析
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境。即并发问题。这个问题相对照较好解决,由于Java语言自身提供了对并发管理的支 持,使用synchronizedkeyword就可以确保线程是同步的。用法为直接在类方法前面加上synchronizedkeyword,如:
public synchronized Connection getConnection()
2、多数据库server和多用户
对于大型的企业级应用。经常须要同一时候连接不同的数据库(如连接Oracle和Sybase)。
怎样连接不同的数据库呢?
我们採用的策略是:设计 一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件。当中资源文件里存放着多个数据库的url地址()?username()?密 码()等信息。如 tx.url=172.21.15.123:5000/tx_it,tx.user=yang。tx.password=yang321。依据资源文件提 供的信息,创建多个连接池类的实例,每个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连 接池。
对于同一个数据库有多个用户使用不同的名称和password訪问的情况,也能够通过资源文件处理,即在资源文件里设置多个具有同样url地址。但具有不同username和password的数据库连接信息。
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 = "";//数据库username private String dbPassword = "";//数据库password private String testTable = ""; private int initialConnectionsNum = 10;//连接池初始连接数 private int maxConnectionsNum = 50;//连接池最大连接数 private int incrementalConnections = 5;//每次动态加入的连接数 private Vector<PooledConnection> connections = null;//向量,存放连接池中的连接,初始为空 /*无參构造函数*/ public Pool() {} /*带參数的构造函数 * 初始化数据库驱动、数据库url、数据库username、数据库password、測试表 * */ 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; } } }