数据库连接池原理

什么是连接?

连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。

有人接受这种说法,却不知道它的真正含义。因此,下面我将解释它究竟是什么。[如果你已经知道了,你可以跳到它的工作原理部分]

创建连接的代码片段:

1
2
3
String connUrl = "jdbc:mysql://your.database.domain/yourDBname";
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection (connUrl);

当我们创建了一个Connection对象,它在内部都执行了什么:

1.“DriverManager”检查并注册驱动程序,
2.“com.mysql.jdbc.Driver”就是我们注册了的驱动程序,它会在驱动程序类中调用“connect(url…)”方法。
3.com.mysql.jdbc.Driver的connect方法根据我们请求的“connUrl”,创建一个“Socket连接”,连接到IP为“your.database.domain”,默认端口3306的数据库。
4.创建的Socket连接将被用来查询我们指定的数据库,并最终让程序返回得到一个结果。

为什么昂贵?

现在让我们谈谈为什么说它“昂贵“。

如果创建Socket连接花费的时间比实际的执行查询的操作所花费的时间还要更长。

这就是我们所说的“数据库连接很昂贵”,因为连接资源数是1,它需要每次创建一个Socket连接来访问DB。

因此,我们将使用连接池。

连接池初始化时创建一定数量的连接,然后从连接池中重用连接,而不是每次创建一个新的。

怎样工作?

接下来我们来看看它是如何工作,以及如何管理或重用现有的连接。

我们使用的连接池供应者,它的内部有一个连接池管理器,当它被初始化:

1.它创建连接池的默认大小,比如指定创建5个连接对象,并把它存放在“可用”状态的任何集合或数组中。

编写连接池需实现java.sql.DataSource接口,
 DataSource接口中定义了两个重载的getConnection方法:
 Connection getConnection()
 Connection getConnection(String username, String password)

实现datasource代码 数据库连接池原理 数据库连接池原理数据库连接池原理
  1. public class JdbcPool implements DataSource {  
  2.   
  3.     private static String driver;  
  4.     private static String url;  
  5.     private static  String username;  
  6.     private static String password;  
  7.   
  8.     static{  
  9.         try {  
  10.               
  11.             Properties prop = new Properties();  
  12.             InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");  
  13.             prop.load(in);  
  14.             driver = prop.getProperty("driver");  
  15.             url = prop.getProperty("url");  
  16.             username = prop.getProperty("username");  
  17.             password = prop.getProperty("password");  
  18.             Class.forName(driver);  
  19.         } catch (Exception e) {  
  20.             throw new ExceptionInInitializerError(e);  
  21.         }  
  22.     }  
  23.       
  24.     private static LinkedList<Connection> pool = new LinkedList<Connection>();  
  25.     private static int poolsize = 10;  
  26.     //问题:每次newJdbcPoll都会建立10个链接,可使用单态设计模式解决此类问题  
  27.     public JdbcPool(){  
  28.       
  29.         for(int i=0;i<poolsize;i++){  
  30.             try {  
  31.                 Connection conn = DriverManager.getConnection(url,username,password);  
  32.                 pool.add(conn);  
  33.                 System.out.println(conn + "被加到池里面了!!!");  
  34.             } catch (SQLException e) {  
  35.                 // TODO Auto-generated catch block  
  36.                 e.printStackTrace();  
  37.             }  
  38.         }  
  39.     }  
  40.       
  41.       
  42.     /*  
  43.      * 这里使用动态代理技术返回一个假的Connection,当dao调用假connection的任何方法时,该方法体内会调用InvocationHandler.invoke方法  
  44.      * 在invoke方法体内,发现dao调用的是close方法,则把链接还到池里,否则,调用真connection的对应方法。  
  45.      * (non-Javadoc)  
  46.      * @see javax.sql.DataSource#getConnection()  
  47.      */  
  48.     public Connection getConnection() throws SQLException { //spring aop  
  49.         if(pool.size()>0){  
  50.             final Connection conn = pool.removeFirst();  
  51.             System.out.println(conn + "从池里面取出去了!!!");  
  52.             return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(),conn.getClass().getInterfaces(), new InvocationHandler(){  
  53.                     //proxy为代理对象 method为要调用的方法 args为方法的参数  
  54.                 public Object invoke(Object proxy, Method method, Object[] args)  
  55.                         throws Throwable {  
  56.                     if(method.getName().equals("close")){  
  57.                         pool.addFirst(conn);  
  58.                         System.out.println(conn + "被还到池里面了!!");  
  59.                         return null;  
  60.                     }else{  
  61.                         return method.invoke(conn, args);  
  62.                     }  
  63.                 }  
  64.             });  
  65.               
  66.         }  
  67.         throw new RuntimeException("对不起,池里没有资源了!!!");  
  68.     }  
  69.   
  70.     public Connection getConnection(String arg0, String arg1)  
  71.             throws SQLException {  
  72.         // TODO Auto-generated method stub  
  73.         return null;  
  74.     }  
  75.   
  76.     public PrintWriter getLogWriter() throws SQLException {  
  77.         // TODO Auto-generated method stub  
  78.         return null;  
  79.     }  
  80.   
  81.     public int getLoginTimeout() throws SQLException {  
  82.         // TODO Auto-generated method stub  
  83.         return 0;  
  84.     }  
  85.   
  86.     public void setLogWriter(PrintWriter arg0) throws SQLException {  
  87.         // TODO Auto-generated method stub  
  88.           
  89.     }  
  90.   
  91.     public void setLoginTimeout(int arg0) throws SQLException {  
  92.         // TODO Auto-generated method stub  
  93.           
  94.     }  
  95.   
  96.     //public Jdbcpool  
  97.       
  98. }  
public class JdbcPool implements DataSource {

	private static String driver;
	private static String url;
	private static  String username;
	private static String password;

	static{
		try {
			
			Properties prop = new Properties();
			InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");
			prop.load(in);
			driver = prop.getProperty("driver");
			url = prop.getProperty("url");
			username = prop.getProperty("username");
			password = prop.getProperty("password");
			Class.forName(driver);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
	
	private static LinkedList<Connection> pool = new LinkedList<Connection>();
	private static int poolsize = 10;
	//问题:每次newJdbcPoll都会建立10个链接,可使用单态设计模式解决此类问题
	public JdbcPool(){
	
		for(int i=0;i<poolsize;i++){
			try {
				Connection conn = DriverManager.getConnection(url,username,password);
				pool.add(conn);
				System.out.println(conn + "被加到池里面了!!!");
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
	/*
	 * 这里使用动态代理技术返回一个假的Connection,当dao调用假connection的任何方法时,该方法体内会调用InvocationHandler.invoke方法
	 * 在invoke方法体内,发现dao调用的是close方法,则把链接还到池里,否则,调用真connection的对应方法。
	 * (non-Javadoc)
	 * @see javax.sql.DataSource#getConnection()
	 */
	public Connection getConnection() throws SQLException { //spring aop
		if(pool.size()>0){
			final Connection conn = pool.removeFirst();
			System.out.println(conn + "从池里面取出去了!!!");
			return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(),conn.getClass().getInterfaces(), new InvocationHandler(){
					//proxy为代理对象 method为要调用的方法 args为方法的参数
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					if(method.getName().equals("close")){
						pool.addFirst(conn);
						System.out.println(conn + "被还到池里面了!!");
						return null;
					}else{
						return method.invoke(conn, args);
					}
				}
			});
			
		}
		throw new RuntimeException("对不起,池里没有资源了!!!");
	}

	public Connection getConnection(String arg0, String arg1)
			throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public PrintWriter getLogWriter() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public int getLoginTimeout() throws SQLException {
		// TODO Auto-generated method stub
		return 0;
	}

	public void setLogWriter(PrintWriter arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void setLoginTimeout(int arg0) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	//public Jdbcpool
	
}

 
 
  一般实现第一个就可了,数据库的信息都 是通过配置文件来实现的,
  通过加载配置文件就可以,取得与数据库的连接

实现DataSource接口,并实现连接池功能的步骤:
 1.在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中
  因为LinkedList 是用链表实现的,对于增删实现起来比较容易
  因为每从池中取出一个连接,都要将这个对象从池不删除,当返回时就要添加回去
  
 2.实现getConnection方法,让getConnection方法每次调用时,
  从LinkedList中取一个Connection返回给用户(即要从池中删除一个对象)
  
 3.当用户使用完Connection,调用Connection.close()方法时,
  Collection对象应保证将自己返回到LinkedList中
  (用户一般都会调用Connection的close方法来关闭连接,从而不能将这个连接返回给池,
  所以这里采用了动态代理技术:赤获取用户关闭连接的操作,当获取到时而不是将其给关闭,
  而是将这个连接(对象)添加到池中)

注:

** 这里使用动态代理技术返回一个假的Connection,
 当dao调用假connection的任何方法时,该方法体内会调用InvocationHandler.invoke方法
** 在invoke方法体内,发现dao调用的是close方法,则把链接还到池里,
 否则,调用真connection的对应方法。

开源的数据库连接池(数据源)

 都提供DataSoruce的实现,即连接池的实现

 DBCP 数据库连接池     C3P0 数据库连接池 
 
 DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
它用到了一个配置文件dbcpconfig.properties(放在类路径下)

用BaseDataSourceFactory 对象 装载这个配置文件

Jdbcutil.java代码 数据库连接池原理 数据库连接池原理数据库连接池原理
  1. private static DataSource dataSource;  
  2.     static {  
  3.         try {  
  4.             InputStream in = JdbcUtil.class.getClassLoader()  
  5.                     .getResourceAsStream("dbcpconfig.properties");  
  6.             Properties prop = new Properties();  
  7.             prop.load(in);  
  8.             BasicDataSourceFactory factory = new BasicDataSourceFactory();  
  9.             dataSource = factory.createDataSource(prop);  
  10.         } catch (Exception e) {  
  11.             throw new ExceptionInInitializerError(e);  
  12.         }  
  13.     }  
  14.       
  15.     public static Connection getConnection() throws SQLException {  
  16.         return dataSource.getConnection();  
  17.     }  
private static DataSource dataSource;
	static {
		try {
			InputStream in = JdbcUtil.class.getClassLoader()
					.getResourceAsStream("dbcpconfig.properties");
			Properties prop = new Properties();
			prop.load(in);
			BasicDataSourceFactory factory = new BasicDataSourceFactory();
			dataSource = factory.createDataSource(prop);
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
	
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}

  

在Tomcat 下实现数据库连接池

1.要在META-INF目录下建一个context.xml文件

Context.xml代码 数据库连接池原理 数据库连接池原理数据库连接池原理
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <Context>  
  3.   <Resource name="jdbc/dataSource" auth="Container"  
  4.             type="javax.sql.DataSource" username="root" password="root"  
  5.             driverClassname="com.mysql.jdbc.Driver"   
  6.         url="jdbc:mysql://localhost:3306/jdbc"  
  7.             maxActive="8" maxIdle="4"/>  
  8. </Context>  
<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <Resource name="jdbc/dataSource" auth="Container"
            type="javax.sql.DataSource" username="root" password="root"
            driverClassName="com.mysql.jdbc.Driver" 
        url="jdbc:mysql://localhost:3306/jdbc"
            maxActive="8" maxIdle="4"/>
</Context>

 
(这是将数据库连接对象绑定到一个名字中去,要用到时就通过这个名字来找这个对象)JNDI
  2.还要将数据库驱动的JAR复制到Tomcat的LIB目录下,因为Tomcat起动时就会去初始化连接池
   所以就会去找相应的JAR文件,其实这个配置是可以配在Tomcat conf目录下server.xml文件中的
   , 为了不改变它原来的配置,所以配置有那也是一样有效的

  3.取得连接,通过命名空间
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/dataSource");

相关推荐