Singleton模式和Proxy模式实现Java数据库连接池全纪录

  学了设计模式之后,就想通过设计模式来实现一下Java数据库连接池……几天的努力,终于搞定,现在和大家分享一下(注:连接池的主要代码还是借鉴的巨人的)!

  Singleton模式和Proxy模式在这就不多谈了。先简单介绍一下整体实现框架:通过Jsp/Servlet对自己做的数据库连接池进行应用,两个jsp页面(一个login.jsp用于登录,一个success.jsp用于显示登录成功后的用户信息)、一个servlet(Check.java用于控制整个数据流程)和两个JavaBean(SQLQuery.java用于处理业务逻辑如验证用户名密码,Userinfo.java用于获取或修改用户信息)。Connection Pool部分三个类,一个连接池管理类(DBConnectionManager),一个连接池类(DBConnectionPool),还有一个代理类(MyConnectionHandler),连接池类内嵌在连接池管理类中,连接池管理类为外覆类。

  在此用的环境是MyEclipse8.5+Apache Tomcat6.0+MySQL5.1,先新建一个Web Project,之后就是填代码了,各代码如下:

DBConnectionManager类(包括DBConnectionPool类)代码:

 

DBConnectionPool.java代码
1 /**
2 * 数据库连接池管理类
3 */
4  package com.Alygle;
5  import java.io.*;
6  import java.sql.*;
7  import java.util.*;
8  import java.util.Date;
9
10  /**
11 * 管理类DBConnectionManager支持对一个或多个由属性文件定义的数据库连接池的
12 * 访问.客户程序可以调用getInstance()方法访问本类的唯一实例
13 */
14  public class DBConnectionManager {
15 //public static void main(String[] args)
16 //{
17 // DBConnectionManager connectionManager = DBConnectionManager.getInstance();
18 // Connection conn = connectionManager.getConnection("test");
19 // try
20 // {
21 // Thread.sleep(10);
22 // }
23 // catch (InterruptedException e)
24 // {
25 // TODO Auto-generated catch block
26 // e.printStackTrace();
27 // }
28 // Connection conn1 = connectionManager.getConnection("test");
29 // Connection conn2 = connectionManager.getConnection("test");
30 // Connection conn3 = connectionManager.getConnection("test");
31 // Connection conn4 = connectionManager.getConnection("test");
32 // Connection conn5 = connectionManager.getConnection("test");
33 // connectionManager.freeConnection("test",conn);
34 // connectionManager.freeConnection("test",conn1);
35 // connectionManager.freeConnection("test",conn2);
36 // connectionManager.freeConnection("test",conn3);
37 // connectionManager.freeConnection("test",conn4);
38 // connectionManager.freeConnection("test",conn5);
39 // Connection conn6 = connectionManager.getConnection("test");
40 // Connection conn7 = connectionManager.getConnection("test");
41 // System.out.println(" conn6 == "+conn6 +" conn7 == "+conn7);
42 //}
43   static private DBConnectionManager instance; // 唯一实例
44   static private int clients;
45 private Vector drivers = new Vector();
46 private PrintWriter log;
47 private Hashtable pools = new Hashtable();
48
49 /**
50 * 返回唯一实例.如果是第一次调用此方法,则创建实例
51 *
52 * @return DBConnectionManager 唯一实例
53 */
54 static synchronized public DBConnectionManager getInstance()
55 {
56 if (instance == null)
57 {
58 instance = new DBConnectionManager();
59 }
60 clients++;
61 return instance;
62 }
63
64 /**
65 * 建构私有函数以防止其它对象创建本类实例
66 */
67 private DBConnectionManager()
68 {
69 init();
70 }
71
72 /**
73 * 将连接对象返回给由名字指定的连接池
74 *
75 * @param name
76 * 在属性文件中定义的连接池名字
77 * @param con
78 * 连接对象
79 */
80 public void freeConnection(String name, Connection con)
81 {
82 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
83 if (pool != null)
84 {
85 pool.freeConnection(con);
86 }
87 }
88
89 /**
90 * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数限制,则创建并返回新连接
91 *
92 * @param name
93 * 在属性文件中定义的连接池名字
94 * @return Connection 可用连接或null
95 */
96 public Connection getConnection(String name)
97 {
98 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
99 //System.out.println(" pool == "+pool);
100   if (pool != null)
101 {
102 return pool.getConnection();
103 }
104 return null;
105 }
106
107 /**
108 * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制, 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.
109 *
110 * @param name
111 * 连接池名字
112 * @param time
113 * 以毫秒计的等待时间
114 * @return Connection 可用连接或null
115 */
116 public Connection getConnection(String name, long time)
117 {
118 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
119 if (pool != null)
120 {
121 return pool.getConnection(time);
122 }
123 return null;
124 }
125
126 /**
127 * 关闭所有连接,撤销驱动程序的注册
128 */
129 public synchronized void release()
130 {
131 // 等待直到最后一个客户程序调用
132   if (--clients != 0)
133 {
134 return;
135 }
136 Enumeration allPools = pools.elements();
137 while (allPools.hasMoreElements())
138 {
139 DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
140 pool.release();
141 }
142 Enumeration allDrivers = drivers.elements();
143 while (allDrivers.hasMoreElements())
144 {
145 Driver driver = (Driver) allDrivers.nextElement();
146 try
147 {
148 DriverManager.deregisterDriver(driver);
149 log("撤销JDBC驱动程序 " + driver.getClass().getName() + "的注册");
150 }
151 catch (SQLException e)
152 {
153 log(e, "无法撤销下列JDBC驱动程序的注册: " + driver.getClass().getName());
154 }
155 }
156 }
157
158 /**
159 * 根据指定属性创建连接池实例.
160 *
161 * @param props
162 * 连接池属性
163 */
164 private void createPools(Properties props)
165 {
166 Enumeration propNames = props.propertyNames();
167 while (propNames.hasMoreElements())
168 {
169 String name = (String) propNames.nextElement();
170 if (name.endsWith(".url"))
171 {
172 String poolName = name.substring(0, name.lastIndexOf("."));
173 //System.out.println(" poolName ||"+poolName+"|");
174   String url = props.getProperty(poolName + ".url");
175 if (url == null)
176 {
177 log("没有为连接池" + poolName + "指定URL");
178 continue;
179 }
180 String user = props.getProperty(poolName + ".user");
181 String password = props.getProperty(poolName + ".password");
182 String maxconn = props.getProperty(poolName + ".maxconn", "0");
183 int max;
184 try
185 {
186 max = Integer.valueOf(maxconn).intValue();
187 }
188 catch (NumberFormatException e)
189 {
190 log("错误的最大连接数限制: " + maxconn + " .连接池: " + poolName);
191 max = 0;
192 }
193 DBConnectionPool pool = new DBConnectionPool(poolName, url, user, password, max);
194 pools.put(poolName, pool);
195 log("成功创建连接池" + poolName);
196 }
197 }
198 }
199
200 /**
201 * 读取属性完成初始化
202 */
203 private void init()
204 {
205 //InputStream fileinputstream = null;
206 //try
207 //{
208 // fileinputstream = new FileInputStream(new File("classpath:db.properties"));
209 //}
210 //catch (FileNotFoundException e1)
211 //{
212 // e1.printStackTrace();
213 //}
214
215 //BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(fileinputstream));
216   InputStream is = getClass().getResourceAsStream("/db.properties");
217 Properties dbProps = new Properties();
218 try
219 {
220 //dbProps.load(fileinputstream);
221   dbProps.load(is);
222 } catch (Exception e)
223 {
224 e.printStackTrace();
225 System.err.println("不能读取属性文件. "+ "请确保db.properties在CLASSPATH指定的路径中");
226 return;
227 }
228 String logFile = dbProps.getProperty("logfile","DBConnectionManager.log");
229 //System.out.println(dbProps.getProperty("logfile"));
230   try
231 {
232 log = new PrintWriter(new FileWriter(logFile, true), true);
233 }
234 catch (IOException e)
235 {
236 System.err.println("无法打开日志文件: " + logFile);
237 log = new PrintWriter(System.err);
238 }
239 loadDrivers(dbProps);
240 createPools(dbProps);
241 }
242
243 /**
244 * 装载和注册所有JDBC驱动程序
245 *
246 * @param props
247 * 属性
248 */
249 private void loadDrivers(Properties props)
250 {
251 String driverClasses = props.getProperty("drivers");
252 StringTokenizer st = new StringTokenizer(driverClasses);
253 while (st.hasMoreElements())
254 {
255 String driverClassName = st.nextToken().trim();
256 try
257 {
258 Driver driver = (Driver) Class.forName(driverClassName).newInstance();
259 DriverManager.registerDriver(driver);
260 drivers.addElement(driver);
261 log("成功注册JDBC驱动程序:" + driverClassName);
262 }
263 catch (Exception e)
264 {
265 log("无法注册JDBC驱动程序: " + driverClassName + ", 错误: " + e);
266 }
267 }
268 }
269
270 /**
271 * 将文本信息写入日志文件
272 */
273 private void log(String msg)
274 {
275 log.println(new Date() + ": " + msg);
276 }
277
278 /**
279 * 将文本信息与异常写入日志文件
280 */
281 private void log(Throwable e, String msg)
282 {
283 log.println(new Date() + ": " + msg);
284 e.printStackTrace(log);
285 }
286
287 /** ************************************************************* */
288 /** ********************内部类DBConnectionPool******************** */
289 /** ************************************************************* */
290 /**
291 * 此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最大连接数为止.在返回连接给客户程序之前,它能够验证连接的有效性.
292 */
293 class DBConnectionPool
294 {
295 public int checkedOut; // 当前连接数
296 public Vector freeConnections = new Vector(); // 保存所有可用连接
297 public int maxconn; // 此连接池允许建立的最大连接数
298 private String name; // 连接池名字
299 private String password; // 密码或null
300 private String url; // 数据库的JDBC URL
301 private String user; // 数据库账号或null
302 /**
303 * 创建新的连接池
304 *
305 * @param name
306 * 连接池名字
307 * @param url
308 * 数据库的JDBC URL
309 * @param user
310 * 数据库帐号或 null
311 * @param password
312 * 密码或 null
313 * @param maxconn
314 * 此连接池允许建立的最大连接数
315 */
316 public DBConnectionPool(String name, String url, String user, String password, int maxconn)
317 {
318 this.name = name;
319 this.url = url;
320 this.user = user;
321 this.password = password;
322 this.maxconn = maxconn;
323 }
324
325 /**
326 * 将不再使用的连接返回给连接池
327 *
328 * @param con
329 * 客户程序释放的连接
330 */
331 public synchronized void freeConnection(Connection con)
332 {
333 // 将指定连接加入到向量末尾
334 freeConnections.addElement(con);
335 checkedOut--;
336 notifyAll(); // 删除等待队列中的所有线程
337 }
338
339 /**
340 * 从连接池获得一个可用连接.如果没有空闲的连接且当前连接数小于最大连接
341 * 数限制,则创建新连接.如原来登记为可用的连接不再有效,则从向量删除之, 然后递归调用自己以尝试新的可用连接.
342 */
343 public synchronized Connection getConnection()
344 {
345 Connection con = null;
346 //System.out.println(" freeConnections.size() "+freeConnections.size());
347 if (freeConnections.size() > 0)
348 {
349 // 获取向量中第一个可用连接
350 con = (Connection) freeConnections.firstElement();
351 freeConnections.removeElementAt(0);
352 try
353 {
354 if (con.isClosed())
355 {
356 log("从连接池" + name + "删除一个无效连接");
357 // 递归调用自己,尝试再次获取可用连接
358 con = getConnection();
359 }
360 }
361 catch (SQLException e)
362 {
363 log("从连接池" + name + "删除一个无效连接");
364 // 递归调用自己,尝试再次获取可用连接
365 con = getConnection();
366 }
367 }
368 else if (maxconn == 0 || checkedOut < maxconn)
369 {
370 con = newConnection();
371 }
372 if (con != null)
373 {
374 checkedOut++;
375 }
376 //System.out.println("con == "+con);
377 return con;
378 }
379
380 /**
381 * 从连接池获取可用连接.可以指定客户程序能够等待的最长时间 参见前一个getConnection()方法.
382 *
383 * @param timeout
384 * 以毫秒计的等待时间限制
385 */
386 public synchronized Connection getConnection(long timeout)
387 {
388 long startTime = new Date().getTime();
389 Connection con;
390 while ((con = getConnection()) == null)
391 {
392 try
393 {
394 wait(timeout);
395 }
396 catch (InterruptedException e) {}
397 if ((new Date().getTime() - startTime) >= timeout)
398 {
399 // wait()返回的原因是超时
400 return null;
401 }
402 }
403 return con;
404 }
405
406 /**
407 * 关闭所有连接
408 */
409 public synchronized void release()
410 {
411 Enumeration allConnections = freeConnections.elements();
412 while (allConnections.hasMoreElements())
413 {
414 Connection con = (Connection) allConnections.nextElement();
415 try
416 {
417 con.close();
418 log("关闭连接池" + name + "中的一个连接");
419 }
420 catch (SQLException e)
421 {
422 log(e, "无法关闭连接池" + name + "中的连接");
423 }
424 }
425 freeConnections.removeAllElements();
426 }
427
428 /**
429 * 创建新的连接
430 */
431 private Connection newConnection()
432 {
433 Connection con = null;
434 //try
435 //{
436 // if (user == null)
437 // {
438 // con = DriverManager.getConnection(url);
439 // }
440 // else
441 // {
442 // con = DriverManager.getConnection(url, user, password);
443 // }
444 // log("连接池" + name + "创建一个新的连接");
445 //}
446 //catch (SQLException e)
447 //{
448 // log(e, "无法创建下列URL的连接: " + url);
449 // return null;
450 //}
451 //return con;
452 try
453 {
454 if(user == null)
455 {
456 con = DriverManager.getConnection(url);
457 }
458 else
459 {
460 con = DriverManager.getConnection(url,user,password);
461 }
462 log("连接池" + name + "创建一个新的连接");
463 }
464 catch(SQLException e)
465 {
466 log(e,"无法创建下列URL的连接:" +url);
467 return null;
468 }
469 MyConnectionHandler proxy = new MyConnectionHandler(this);
470 return proxy.bind(con);
471 }
472 }
473 }

MyConnectionHandler类代码:

 

代码
package com.Alygle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

public class MyConnectionHandler implements InvocationHandler{
private DBConnectionManager.DBConnectionPool myds;
private Connection con;
private Connection proxyConn;
MyConnectionHandler(DBConnectionManager.DBConnectionPool myds)
{
this.myds = myds;
}
Connection bind(Connection realConn)
{
this.con = realConn;
//建立连接对象的代理
this.proxyConn = (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, this);
return proxyConn;
}

/**
* @override
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
if("close".equals(method.getName())) //如果是close方法
{
this.myds.checkedOut++;
if(this.myds.checkedOut < this.myds.maxconn)
{
this.myds.freeConnections.add(this.proxyConn);
}
else
{
this.con.close();
this.myds.checkedOut--;
}
}
//如果不是close方法则动态调用传入的数据库驱动的Connection对象的方法
return method.invoke(this.con, args);
}
}

属性配置文件db.properties:

drivers=org.gjt.mm.mysql.Driver
logfile=D\:\\Java\\Genuitec\\Workspaces\\MyEclipse 8.5\\Login\\JavaDBConnectPool-log.txt
mysql.maxconn=100
mysql.url=jdbc:mysql://localhost:3306/test
mysql.user=root
mysql.password=root

web.xml配置代码:

 

web.xml代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>Check</servlet-name>
<servlet-class>mypack.Check</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Check</servlet-name>
<url-pattern>/Check</url-pattern>
</servlet-mapping>

</web-app>

 下面是用jsp/servlet测试连接池的代码。

Check.java代码:

 

Check.java代码
package mypack;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Check extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 7261651425231069629L;
public Check()
{
super();
}
public void destroy()
{
super.destroy();
}
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException{}
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException
{
String username
= (String) request.getParameter("username");
String password
= (String) request.getParameter("password");

if(!SQLQuery.validate(username,password))
{
request.setAttribute(
"error.message","非法登录!");
RequestDispatcher requestDispatcher
= request.getRequestDispatcher("login.jsp");
requestDispatcher.forward(request, response);
}
else
{
request.getSession().setAttribute(
"username",username);
RequestDispatcher requestDispatcher
= request.getRequestDispatcher("success.jsp");
requestDispatcher.forward(request, response);
}
}

}

SQLQuery.java代码:

 

SQLQuery.java代码
package mypack;
import java.sql.Connection;
//import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.ResultSet;
import com.mysql.jdbc.Statement;
import com.Alygle.*;

public class SQLQuery { //用于处理业务逻辑的JavaBean
public static boolean validate(String username,String password)
{
//用于验证用户名密码是否正确
boolean bool = false;
//try
//{
// Class.forName("org.gjt.mm.mysql.Driver");
//}
//catch (java.lang.ClassNotFoundException e)
//{
// System.out.print("Class not found exception occur. Message is:");
// System.out.print(e.getMessage());
//}
try
{
Statement stmt;
ResultSet rs;
DBConnectionManager connectionManager
= DBConnectionManager.getInstance();
//String url = "jdbc:mysql://localhost:3306/test";
//String user = "root";
//String passwordOfDB = "root";
Connection conn = null;
conn
= connectionManager.getConnection("mysql");
//try
//{
// conn = DriverManager.getConnection(url,user,passwordOfDB);
//}
//catch (Exception ex)
//{
// ex.printStackTrace();
//}
stmt = (Statement) conn.createStatement();
rs
= (ResultSet) stmt.executeQuery(
"select * from userinfo where username = '"+username+"'and password = '"+password+"'");
if(rs.next()) bool = true;
else bool = false;
}
catch (SQLException e)
{
System.out.print(
"SQL Exception occur. Message is:");
System.out.print(e.getMessage());
}
return bool;
}
public static Userinfo userinfoQuery(String username)
{
Userinfo ui
= new Userinfo();
//try
//{
// Class.forName("org.gjt.mm.mysql.Driver");
//}
//catch (java.lang.ClassNotFoundException e)
//{
// System.out.print("Class not found exception occur. Message is:");
// System.out.print(e.getMessage());
//}
//连接数据库并获取数据
try
{
Statement stmt;
ResultSet rs;
DBConnectionManager connectionManager
= DBConnectionManager.getInstance();
//String url = "jdbc:mysql://localhost:3306/test";
//String user = "root";
//String passwordOfDB = "root";
//连接数据库
Connection conn = null;
try
{
//conn = DriverManager.getConnection(url,user,passwordOfDB);
conn = connectionManager.getConnection("mysql");
}
catch (Exception ex) {ex.printStackTrace();}
stmt
= (Statement) conn.createStatement();
rs
= (ResultSet) stmt.executeQuery(
"select * from userinfo where username = '"+username+"'");
while(rs.next())
{
ui.setName(rs.getString(
2));
ui.setAge(rs.getInt(
4));
ui.setGender(rs.getString(
5));
ui.setEmail(rs.getString(
6));
}
}
catch (SQLException e)
{
System.out.print(
"SQL Exception occur. Message is:");
System.out.print(e.getMessage());
}
return ui;
}

}

 

Userinfo.java代码:

 

Userinfo.java代码
package mypack;

public class Userinfo { //用于获取或修改用户信息的JavaBean
private String name = null;
private String password = null;
private int age = 0;
private String gender = null;
private String email = null;
public String getName()
{
return name;
}
public String getPassword()
{
return password;
}
public int getAge()
{
return age;
}
public String getGender()
{
return gender;
}
public String getEmail()
{
return email;
}

public void setName(String name)
{
this.name = name;
}
public void setPassword(String password)
{
this.password = password;
}
public void setAge(int age)
{
this.age = age;
}
public void setGender(String gender)
{
this.gender = gender;
}
public void setEmail(String email)
{
this.email = email;
}

public void init(String username)
{
Userinfo ui
= SQLQuery.userinfoQuery(username);
this.name = ui.name;
this.password = ui.password;
this.age = ui.age;
this.gender = ui.gender;
this.email = ui.email;
}

}

jsp页面

login.jsp代码:

<%@ page language="java" import="java.util.*" pageEncoding="GB2312"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Login Page</title>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
  </head>
  <body>
  <div align="center">
   <form name="regForm" method="post" action="Check">
    Name<input name="username" type="text" id="username"><br/>
    Password<input name="password" type="password" id="password"><br/>
   <input type="Submit" name="submit" value="Submit"/>
   <input type="Reset" name="reset" value="reset"/>
   <br/>
   <br/>
   <font color="red">
   <%
   if(!((String)request.getAttribute("error.message") == null)){
    out.print(request.getAttribute("error.message"));
   }
    %>
    </font>
    </form>
  </div>
  </body>
</html>

 

success.jsp代码:

<%@ page language="java" import="java.util.*" pageEncoding="GB2312"%>

<html>
  <head>
    <title>登陆成功</title>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
  </head>
  <jsp:useBean id="userinfo" scope="page" class="mypack.Userinfo"></jsp:useBean>
 
  <body>
  <div align="center">
   登陆成功!<br>
   <%userinfo.init((String)session.getAttribute("username")); %>
   用户:<jsp:getProperty name="userinfo" property="name"/><br>
   年龄:<jsp:getProperty name="userinfo" property="age" /><br>
   性别:<jsp:getProperty name="userinfo" property="gender"/><br>
   邮箱:<jsp:getProperty name="userinfo" property="email"/><br>
  </div>
  </body>
</html>

在MySql中创建数据库,代码如下:(MySql的用户名和密码都是root,如果不是,就在属性文件里改一下)

create database test;
use test
create table userinfo
(id int primary key,
 userinfo varchar(16),
 password varchar(16),
 age int,
 gender varchar(8),
 email varchar(45));
insert into userinfo values(10001,'Alygle','123456',23,'male','Alygle2009@hotmail.com');
insert into userinfo values(10002,'admin','admin',25,'female','Alygle.chang@gmail.com');

  最后记得加入MySql数据库驱动mysql-connector-java-5.1.10-bin.jar,启动tomcat服务器,在地址栏输入http://localhost:8080/Login/login.jsp就行了。对数据库连接池的操作记录在根目录下的JavaDBConnectPool-log.txt里有记录,如果属性文件里没有指明这个文件,就会默认记录在DBConnectionManager.log这一文件里。

  以上就是Connection Pool的全过程了,如有不足之处望高人纠正批评。

posted on 2010-05-29 10:10  Alygle  阅读(959)  评论(0编辑  收藏  举报

导航