现在的位置: 首页 > 综合 > 正文

关于JSP连接池

2014年03月24日 ⁄ 综合 ⁄ 共 35022字 ⁄ 字号 评论关闭
 到目前为目,JDBC2的连结池只是一个接口,没有真正的实现,JDBC3正在开发中,据报已经支持连结池,但..........
JDBC3用了JNDI技术,连结池的配置可以让一个高手都烦死.

目前第三方已经实现的连结池当然是poolman,1.0版对一般用户来说已经足够用了.配置也简单,2.0版虽然增加了一些功能,但配置也是采用JNDI,对RMI和EJB不懂的朋友可能很烦.建议用1.0的了.

如果有兴趣,自己也可以实现连结池,最关键的技术也就是把连结作为参数传给一个BEAN,用完后返回这个参数连结而不是关闭.
下面是一个简单的实现:
DBConnectionManager.java程序清单如下:
    
    001 import java.io.*;
    002 import java.sql.*;
    003 import java.util.*;
    004 import java.util.Date;
    005
    006 /**
    007 * 管理类DBConnectionManager支持对一个或多个由属性文件定义的数据库连接
    008 * 池的访问.客户程序可以调用getInstance()方法访问本类的唯一实例.
    009 */
    010 public class DBConnectionManager {
    011 static private DBConnectionManager instance; // 唯一实例
    012 static private int clients;
    013
    014 private Vector drivers = new Vector();
    015 private PrintWriter log;
    016 private Hashtable pools = new Hashtable();
    017
    018 /**
    019 * 返回唯一实例.如果是第一次调用此方法,则创建实例
    020 *
    021 * @return DBConnectionManager 唯一实例
    022 */
    023 static synchronized public DBConnectionManager getInstance() {
    024 if (instance == null) {
    025 instance = new DBConnectionManager();
    026 }
    027 clients++;
    028 return instance;
    029 }
    030
    031 /**
    032 * 建构函数私有以防止其它对象创建本类实例
    033 */
    034 private DBConnectionManager() {
    035 init();
    036 }
    037
    038 /**
    039 * 将连接对象返回给由名字指定的连接池
    040 *
    041 * @param name 在属性文件中定义的连接池名字
    042 * @param con 连接对象/

    043 */
    044 public void freeConnection(String name, Connection con) {
    045 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
    046 if (pool != null) {
    047 pool.freeConnection(con);
    048 }
    049 }
    050
    051 /**
    052 * 获得一个可用的(空闲的)连接.如果没有可用连接,且已有连接数小于最大连接数
    053 * 限制,则创建并返回新连接
    054 *
    055 * @param name 在属性文件中定义的连接池名字
    056 * @return Connection 可用连接或null
    057 */
    058 public Connection getConnection(String name) {
    059 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
    060 if (pool != null) {
    061 return pool.getConnection();
    062 }
    063 return null;
    064 }
    065
    066 /**
    067 * 获得一个可用连接.若没有可用连接,且已有连接数小于最大连接数限制,
    068 * 则创建并返回新连接.否则,在指定的时间内等待其它线程释放连接.
    069 *
    070 * @param name 连接池名字
    071 * @param time 以毫秒计的等待时间/

    072 * @return Connection 可用连接或null
    073 */
    074 public Connection getConnection(String name, long time) {
    075 DBConnectionPool pool = (DBConnectionPool) pools.get(name);
    076 if (pool != null) {
    077 return pool.getConnection(time);
    078 }
    079 return null;
    080 }
    081
    082 /**
    083 * 关闭所有连接,撤销驱动程序的注册/

    084 */
    085 public synchronized void release() {
    086 // 等待直到最后一个客户程序调用
    087 if (--clients != 0) {
    088 return;
    089 }
    090
    091 Enumeration allPools = pools.elements();
    092 while (allPools.hasMoreElements()) {
    093 DBConnectionPool pool = (DBConnectionPool) allPools.nextElement();
    094 pool.release();
    095 }
    096 Enumeration allDrivers = drivers.elements();
    097 while (allDrivers.hasMoreElements()) {
    098 Driver driver = (Driver) allDrivers.nextElement();
    099 try {
    100 DriverManager.deregisterDriver(driver);
    101 log("撤销JDBC驱动程序 " + driver.getClass().getName()+"的注册///");
    102 }
    103 catch (SQLException e) {
    104 log(e, "无法撤销下列JDBC驱动程序的注册: " + driver.getClass().getName());
    105 }
    106 }
    107 }
    108
    109 /**
    110 * 根据指定属性创建连接池实例.
    111 *
    112 * @param props 连接池属性
    113 */
    114 private void createPools(Properties props) {
    115 Enumeration propNames = props.propertyNames();
    116 while (propNames.hasMoreElements()) {
    117 String name = (String) propNames.nextElement();
    118 if (name.endsWith(".url")) {
    119 String poolName = name.substring(0, name.lastIndexOf("."));
    120 String url = props.getProperty(poolName + ".url");
    121 if (url == null) {
    122 log("没有为连接池" + poolName + "指定URL");
    123 continue;
    124 }
    125 String user = props.getProperty(poolName + ".user");
    126 String password = props.getProperty(poolName + ".password");
    127 String maxconn = props.getProperty(poolName + ".maxconn", "0");
    128 int max;
    129 try {
    130 max = Integer.valueOf(maxconn).intValue();
    131 }
    132 catch (NumberFormatException e) {
    133 log("错误的最大连接数限制: " + maxconn + " .连接池: " + poolName);
    134 max = 0;
    135 }
    136 DBConnectionPool pool =
    137 new DBConnectionPool(poolName, url, user, password, max);
    138 pools.put(poolName, pool);
    139 log("成功创建连接池" + poolName);
    140 }
    141 }
    142 }
    143
    144 /**
    145 * 读取属性完成初始化
    146 */
    147 private void init() {
    148 InputStream is = getClass().getResourceAsStream("/db.properties");
    149 Properties dbProps = new Properties();
    150 try {
    151 dbProps.load(is);
    152 }
    153 catch (Exception e) {
    154 System.err.println("不能读取属性文件. " +
    155 "请确保db.properties在CLASSPATH指定的路径中");
    156 return;
    157 }
    158 String logFile = dbProps.getProperty("logfile", "DBConnectionManager.log");
    159 try {
    160 log = new PrintWriter(new FileWriter(logFile, true), true);
    161 }
    162 catch (IOException e) {
    163 System.err.println("无法打开日志文件: " + logFile);
    164 log = new PrintWriter(System.err);
    165 }
    166 loadDrivers(dbProps);
    167 createPools(dbProps);
    168 }
    169
    170 /**
    171 * 装载和注册所有JDBC驱动程序/

    172 *
    173 * @param props 属性
    174 */
    175 private void loadDrivers(Properties props) {
    176 String driverClasses = props.getProperty("drivers");
    177 StringTokenizer st = new StringTokenizer(driverClasses);
    178 while (st.hasMoreElements()) {
    179 String driverClassName = st.nextToken().trim();
    180 try {
    181 Driver driver = (Driver)
    182 Class.forName(driverClassName).newInstance();
    183 DriverManager.registerDriver(driver);
    184 drivers.addElement(driver);
    185 log("成功注册JDBC驱动程序///" + driverClassName);
    186 }
    187 catch (Exception e) {
    188 log("无法注册JDBC驱动程序: " +
    189 driverClassName + ", 错误: " + e);
    190 }
    191 }
    192 }
    193
    194 /**
    195 * 将文本信息写入日志文件
    196 */
    197 private void log(String msg) {
    198 log.println(new Date() + ": " + msg);
    199 }
    200
    201 /**
    202 * 将文本信息与异常写入日志文件
    203 */
    204 private void log(Throwable e, String msg) {
    205 log.println(new Date() + ": " + msg);
    206 e.printStackTrace(log);
    207 }
    208
    209 /**
    210 * 此内部类定义了一个连接池.它能够根据要求创建新连接,直到预定的最/

    211 * 大连接数为止.在返回连接给客户程序之前,它能够验证连接的有效性.
    212 */
    213 class DBConnectionPool {
    214 private int checkedOut;
    215 private Vector freeConnections = new Vector();
    216 private int maxConn;
    217 private String name;
    218 private String password;
    219 private String URL;
    220 private String user;
    221
    222 /**
    223 * 创建新的连接池
    224 *
    225 * @param name 连接池名字
    226 * @param URL 数据库的JDBC URL
    227 * @param user 数据库帐号,或 null
    228 * @param password 密码,或 null
    229 * @param maxConn 此连接池允许建立的最大连接数
    230 */
    231 public DBConnectionPool(String name, String URL, String user, String password,
    232 int maxConn) {
    233 this.name = name;
    234 this.URL = URL;
    235 this.user = user;
    236 this.password = password;
    237 this.maxConn = maxConn;
    238 }
    239
    240 /**
    241 * 将不再使用的连接返回给连接池
    242 *
    243 * @param con 客户程序释放的连接
    244 */
    245 public synchronized void freeConnection(Connection con) {
    246 // 将指定连接加入到向量末尾
    247 freeConnections.addElement(con);
    248 checkedOut--;
    249 notifyAll();
    250 }
    251
    252 /**
    253 * 从连接池获得一个可用连接.如没有空闲的连接且当前连接数小于最大连接
    254 * 数限制,则创建新连接.如原来登记为可用的连接不再有效,则从向量删除之,
    255 * 然后递归调用自己以尝试新的可用连接.
    256 */
    257 public synchronized Connection getConnection() {
    258 Connection con = null;
    259 if (freeConnections.size() > 0) {
    260 // 获取向量中第一个可用连接
    261 con = (Connection) freeConnections.firstElement();
    262 freeConnections.removeElementAt(0);
    263 try {
    264 if (con.isClosed()) {
    265 log("从连接池" + name+"删除一个无效连接");
    266 // 递归调用自己,尝试再次获取可用连接
    267 con = getConnection();
    268 }
    269 }
    270 catch (SQLException e) {
    271 log("从连接池" + name+"删除一个无效连接");
    272 // 递归调用自己,尝试再次获取可用连接
    273 con = getConnection();
    274 }
    275 }
    276 else if (maxConn == 0 || checkedOut < maxConn) {
    277 con = newConnection();
    278 }
    279 if (con != null) {
    280 checkedOut++;
    281 }
    282 return con;
    283 }
    284
    285 /**
    286 * 从连接池获取可用连接.可以指定客户程序能够等待的最长时间/

    287 * 参见前一个getConnection()方法.
    288 *
    289 * @param timeout 以毫秒计的等待时间限制
    290 */
    291 public synchronized Connection getConnection(long timeout) {
    292 long startTime = new Date().getTime();
    293 Connection con;
    294 while ((con = getConnection()) == null) {
    295 try {
    296 wait(timeout);
    297 }
    298 catch (InterruptedException e) {}
    299 if ((new Date().getTime() - startTime) >= timeout) {
    300 // wait()返回的原因是超时
    301 return null;
    302 }
    303 }
    304 return con;
    305 }
    306
    307 /**
    308 * 关闭所有连接
    309 */
    310 public synchronized void release() {
    311 Enumeration allConnections = freeConnections.elements();
    312 while (allConnections.hasMoreElements()) {
    313 Connection con = (Connection) allConnections.nextElement();
    314 try {
    315 con.close();
    316 log("关闭连接池" + name+"中的一个连接");
    317 }
    318 catch (SQLException e) {
    319 log(e, "无法关闭连接池" + name+"中的连接");
    320 }
    321 }
    322 freeConnections.removeAllElements();
    323 }
    324
    325 /**
    326 * 创建新的连接
    327 */
    328 private Connection newConnection() {
    329 Connection con = null;
    330 try {
    331 if (user == null) {
    332 con = DriverManager.getConnection(URL);
    333 }
    334 else {
    335 con = DriverManager.getConnection(URL, user, password);
    336 }
    337 log("连接池" + name+"创建一个新的连接");
    338 }
    339 catch (SQLException e) {
    340 log(e, "无法创建下列URL的连接: " + URL);
    341 return null;
    342 }
    343 return con;
    344 }
    345 }
    346 }

    三、类DBConnectionPool说明/
    
    该类在209至345行实现,它表示指向某个数据库的连接池。数据库由JDBC URL标识。一个JDBC URL由三部分组成:协议标识(总是jdbc),驱动程序标识(如 odbc、idb、oracle等),数据库标识(其格式依赖于驱动程序)。例如,jdbc:odbc:demo,即是一个指向demo数据库的JDBC URL,而且访问该数据库要使用JDBC-ODBC驱动程序。每个连接池都有一个供客户程序使用的名字以及可选的用户帐号、密码、最大连接数限制。如果Web应用程序所支持的某些数据库操作可以被所有用户执行,而其它一些操作应由特别许可的用户执行,则可以为两类操作分别定义连接池,两个连接池使用相同的JDBC URL,但使用不同的帐号和密码。
    类DBConnectionPool的建构函数需要上述所有数据作为其参数。如222至238行所示,这些数据被保存为它的实例变量:
    如252至283行、285至305行所示, 客户程序可以使用DBConnectionPool类提供的两个方法获取可用连接。两者的共同之处在于:如连接池中存在可用连接,则直接返回,否则创建新的连接并返回。如果没有可用连接且已有连接总数等于最大限制数,第一个方法将直接返回null,而第二个方法将等待直到有可用连接为止。
    所有的可用连接对象均登记在名为freeConnections的向量(Vector)中。如果向量中有多于一个的连接,getConnection()总是选取第一个。同时,由于新的可用连接总是从尾部加入向量,从而使得数据库连接由于长时间闲置而被关闭的风险减低到最小程度。
    第一个getConnection()在返回可用连接给客户程序之前,调用了isClosed()方法验证连接仍旧有效。如果该连接被关闭或触发异常,getConnection()递归地调用自己以尝试获取另外的可用连接。如果在向量freeConnections中不存在任何可用连接,getConnection()方法检查是否已经指定最大连接数限制。如已经指定,则检查当前连接数是否已经到达极限。此处maxConn为0表示没有限制。如果没有指定最大连接数限制或当前连接数小于该值,该方法尝试创建新的连接。如创建成功,则增加已使用连接的计数并返回,否则返回空值。
    如325至345行所示,创建新连接由newConnection()方法实现。创建过程与是否已经指定数据库帐号、密码有关。
    JDBC的DriverManager类提供多个getConnection()方法,这些方法要用到JDBC URL与其它一些参数,如用户帐号和密码等。DriverManager将使用指定的JDBC URL确定适合于目标数据库的驱动程序及建立连接。
    在285至305行实现的第二个getConnection()方法需要一个以毫秒为单位的时间参数,该参数表示客户程序能够等待的最长时间。建立连接的具体操作仍旧由第一个getConnection()方法实现。
    该方法执行时先将startTime初始化为当前时间。在while循环中尝试获得一个连接。如果失败,则以给定的时间值为参数调用wait()。wait()的返回可能是由于其它线程调用notify()或notifyAll(),也可能是由于预定时间已到。为找出wait()返回的真正原因,程序用当前时间减开始时间(startTime),如差值大于预定时间则返回空值,否则再次调用getConnection()。
    把空闲的连接登记到连接池由240至250行的freeConnection()方法实现,它的参数为返回给连接池的连接对象。该对象被加入到freeConnections向量的末尾,然后减少已使用连接计数。调用notifyAll()是为了通知其它正在等待可用连接的线程。
    许多Servlet引擎为实现安全关闭提供多种方法。数据库连接池需要知道该事件以保证所有连接能够正常关闭。DBConnectionManager类负协调整个关闭过程,但关闭连接池中所有连接的任务则由DBConnectionPool类负责。在307至323行实现的release()方法供DBConnectionManager调用。该方法遍历freeConnections向量并关闭所有连接,然后从向量中删除这些连接。
    
    
    四、类DBConnectionManager 说明/
    
    该类只能创建一个实例,其它对象能够调用其静态方法(也称为类方法)获得该唯一实例的引用。如031至036行所示,DBConnectionManager类的建构函数是私有的,这是为了避免其它对象创建该类的实例。
    DBConnectionManager类的客户程序可以调用getInstance()方法获得对该类唯一实例的引用。如018至029行所示,类的唯一实例在getInstance()方法第一次被调用期间创建,此后其引用就一直保存在静态变量instance中。每次调用getInstance()都增加一个DBConnectionManager的客户程序计数。即,该计数代表引用DBConnectionManager唯一实例的客户程序总数,它将被用于控制连接池的关闭操作。
    该类实例的初始化工作由146至168行之间的私有方法init()完成。其中 getResourceAsStream()方法用于定位并打开外部文件。外部文件的定位方法依赖于类装载器的实现。标准的本地类装载器查找操作总是开始于类文件所在路径,也能够搜索CLASSPATH中声明的路径。db.properties是一个属性文件,它包含定义连接池的键-值对。可供定义的公用属性如下:
    
    drivers 以空格分隔的JDBC驱动程序类列表/
    logfile 日志文件的绝对路径
    
    其它的属性和特定连接池相关,其属性名字前应加上连接池名字:
    
    < poolname>.url 数据库的 JDBC URL 
    < poolname>.maxconn 允许建立的最大连接数,0表示没有限制 
    < poolname>.user 用于该连接池的数据库帐号
    < poolname>.password 相应的密码/
    
    其中url属性是必需的,而其它属性则是可选的。数据库帐号和密码必须合法。用于Windows平台的db.properties文件示例如下:
    
    drivers=sun.jdbc.odbc.JdbcOdbcDriver jdbc.idbDriver
    logfile=D://user//src//java//DBConnectionManager//log.txt
    
    idb.url=jdbc:idb:c://local//javawebserver1.1//db//db.prp
    idb.maxconn=2
    
    access.url=jdbc:odbc:demo
    access.user=demo
    access.password=demopw
    
    注意在Windows路径中的反斜杠必须输入2个,这是由于属性文件中的反斜杠同时也是一个转义字符。
    init()方法在创建属性对象并读取db.properties文件之后,就开始检查logfile属性。如果属性文件中没有指定日志文件,则默认为当前目录下的DBConnectionManager.log文件。如日志文件无法使用,则向System.err输出日志记录。
    装载和注册所有在drivers属性中指定的JDBC驱动程序由170至192行之间的loadDrivers()方法实现。该方法先用StringTokenizer将drivers属性值分割为对应于驱动程序名称的字符串,然后依次装载这些类并创建其实例,最后在 DriverManager中注册该实例并把它加入到一个私有的向量drivers。向量drivers将用于关闭服务时从DriverManager取消所有JDBC 驱动程序的注册。
    init()方法的最后一个任务是调用私有方法createPools()创建连接池对象。如109至142行所示,createPools()方法先创建所有属性名字的枚举对象(即Enumeration对象,该对象可以想象为一个元素系列,逐次调用其nextElement()方法将顺序返回各元素),然后在其中搜索名字以“.url”结尾的属性。对于每一个符合条件的属性,先提取其连接池名字部分,进而读取所有属于该连接池的属性,最后创建连接池对象并把它保存在实例变量pools中。散列表(Hashtable类 )pools实现连接池名字到连接池对象之间的映射,此处以连接池名字为键,连接池对象为值。
    为便于客户程序从指定连接池获得可用连接或将连接返回给连接池,类DBConnectionManager提供了方法getConnection()和freeConnection()。所有这些方法都要求在参数中指定连接池名字,具体的连接获取或返回操作则调用对应的连接池对象完成。它们的实现分别在051至064行、066至080行、038至049行。
    如082至107行所示,为实现连接池的安全关闭,DBConnectionManager提供了方法release()。在上面我们已经提到,所有DBConnectionManager的客户程序都应该调用静态方法getInstance()以获得该管理器的引用,此调用将增加客户程序计数。客户程序在关闭时调用release()可以递减该计数。当最后一个客户程序调用release(),递减后的引用计数为0,就可以调用各个连接池的release()方法关闭所有连接了。管理类release()方法最后的任务是撤销所有JDBC驱动程序的注册。
    
    
    五、Servlet使用连接池示例
    
    Servlet API所定义的Servlet生命周期类如:
    
    1) 创建并初始化Servlet(init()方法)。
    2) 响应客户程序的服务请求(service()方法)。
    3) Servlet终止运行,释放所有资源(destroy()方法)。
    
    本例演示连接池应用,上述关键步骤中的相关操作为:
    
    1) 在init(),用实例变量connMgr 保存调用DBConnectionManager.getInstance()所返回的引用。
    2) 在service(),调用getConnection(),执行数据库操作,用freeConnection()将连接返回给连接池。
    3) 在destroy(),调用release()关闭所有连接,释放所有资源。
    
    示例程序清单如下:
    

CODE:


import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class TestServlet extends HttpServlet {
    private DBConnectionManager connMgr;
    public void init(ServletConfig conf) throws ServletException {
        super.init(conf);
        connMgr = DBConnectionManager.getInstance();
    }
    public void service(HttpServletRequest req, HttpServletResponse res)
        throws IOException {
        res.setContentType("text/html");
        PrintWriter out = res.getWriter();
        Connection con = connMgr.getConnection("idb");
        if (con == null) {
            out.println("不能获取数据库连接.");
            return;
        }
        ResultSet rs = null;
        ResultSetMetaData md = null;
        Statement stmt = null;
        try {
            stmt = con.createStatement();
            rs = stmt.executeQuery("SELECT * FROM EMPLOYEE");
            md = rs.getMetaData();
            out.println("< H1>职工数据< /H1>");
            while (rs.next()) {
                out.println("< BR>");
                for (int i = 1; i < md.getColumnCount(); i++) {
                    out.print(rs.getString(i) + ", ");
                }
            }
            stmt.close();
            rs.close();
        } catch (SQLException e) {
            e.printStackTrace(out);
        }
        connMgr.freeConnection("idb", con);
    }
    public void destroy() {
        connMgr.release();
        super.destroy();
    }
}



发表于 @ 2006年03月17日 11:29 PM | 评论 (0)

从数据库中读出图片并显示的示例代码

< !-- -- -- -- -- -- -- -- -- -- -- -- -- --servlet-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->
package Photo;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.lang.*;
import java.sql.*;

/**
* <p>Title: </p>
* <p>Description: </p>
* <p>Copyright: Copyright (c) 2002</p>
* <p>Company: </p>
* @author unascribed
* @version 1.0
*/

public class ShowImage extends HttpServlet {
    private static final String CONTENT_TYPE = "image/*";
    /**
    * 定义数据库连接字符串,jdbc.odbc桥
    */
    private String driver_class = "oracle.jdbc.driver.OracleDriver";
    private String connect_string =
        "jdbc:oracle:thin:xxw/xxw@192.168.1.50:1521:ORCL";
    Connection conn = null;
    ResultSet rs = null;
    Statement stmt = null;
    /********************************************
    * 定义应用变量
    ******************************************/
    private String SQLString = ""; //定义查询语句
    public String M_EorrMenage = ""; //定义错误信息变量
    private InputStream in = null; //定义输入流
    private int len = 10 * 1024 * 1024; //定义字符数组长度

    //Initialize global variables
    public void init() throws ServletException {
        /**
        * 连接数据库
        */
        try {
            Class.forName(driver_class);
        } catch (java.lang.ClassNotFoundException e) {
            //异常
            System.err.println("databean():" + e.getMessage());
        }
    }
    //Process the HTTP Get request
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        response.setContentType(CONTENT_TYPE);
        PrintWriter out = response.getWriter();
        //在数据库中的照片的ID
        int PHOTOID = 0;
        /*********************************************
        * 接受上文传递的图片ID号
        * 上文传输文件名称为photoid
        *********************************************/
        try {

            PHOTOID = Integer.parseInt(request.getParameter("photoid"));
            SQLString = "select * from xxw_photo where p_id=" + PHOTOID;

        } catch (Exception e) {
            e.printStackTrace();
            response.setContentType("text/html; charset=gb2312");
            M_EorrMenage = "请输入图片ID号";
            M_EorrMenage =
                new String(M_EorrMenage.getBytes("ISO8859_1"), "GBK");
            out.println("<%@ page contentType='text/html; charset=gb2312' %>");
            out.println("<html>");
            out.println("<head><title>id</title></head>");
            out.println("<body>");
            out.println("<p>" + M_EorrMenage + "</p>");
            out.println("</body></html>");

        }
        /*****************************************************
        * 执行查询语句
        *****************************************************/
        try {
            conn = DriverManager.getConnection(connect_string);
            stmt = conn.createStatement();
            rs = stmt.executeQuery(SQLString);
        } //try
        catch (SQLException ex) {
            System.err.println("aq.executeUpdate:" + ex.getMessage());
            M_EorrMenage = "对不起,数据库无法完成此操作!";
            M_EorrMenage =
                new String(M_EorrMenage.getBytes("ISO8859_1"), "GBK");
            response.setContentType("text/html; charset=gb2312");
            out.println("<html>");
            out.println("<head><title>no_database</title></head>");
            out.println("<body>");
            out.println("<p>" + M_EorrMenage + "</p>");
            out.println("</body></html>");

        }
        /*********************************************
        * 将图片流读入字符数组中,并显示到客户端
        ********************************************/
        try {
            if (rs.next()) {
                in = rs.getBinaryStream("photo");
                response.reset(); //返回在流中被标记过的位置
                response.setContentType("image/jpg"); //或gif等
                // int len=in.available();//得到文件大小
                OutputStream toClient = response.getOutputStream();
                byte[] P_Buf = new byte[len];
                int i;
                while ((i = in.read(P_Buf)) != -1) {
                    toClient.write(P_Buf, 0, i);
                }
                in.close();
                toClient.flush(); //强制清出缓冲区
                toClient.close();
            } else {
                M_EorrMenage = "无此图片!";
                M_EorrMenage =
                    new String(M_EorrMenage.getBytes("ISO8859_1"), "GBK");
                response.setContentType("text/html; charset=gb2312");
                out.println("<html>");
                out.println(
                    "<head><title>this photo isn't have</title></head>");
                out.println("<body>");
                out.println("<p>" + M_EorrMenage + "</p>");
                out.println("</body></html>");
            }
            rs.close();
        } catch (Exception e) {
            e.printStackTrace();
            M_EorrMenage = "无法读取图片!";
            M_EorrMenage =
                new String(M_EorrMenage.getBytes("ISO8859_1"), "GBK");
            response.setContentType("text/html; charset=gb2312");
            out.println("<%@ page contentType='text/html; charset=gb2312' %>");
            out.println("<html>");
            out.println("<head><title>no photo</title></head>");
            out.println("<body>");
            out.println("<p>" + M_EorrMenage + "</p>");
            out.println("</body></html>");
        }
    }

    //Clean up resources
    public void destroy() {
        try {
            conn.close();
        } catch (SQLException e) {
            System.err.println("aq.executeUpdate:" + e.getMessage());
            M_EorrMenage = "对不起,数据库无法完成此操作!";
        }
    }
}



<!---------------------------显示---------------------------------------------->
<html>
<head>
<title>Untitled Document</title>
</head>
<body bgcolor="#FFFFFF" text="#000000">
<table>
<%
int i=1;
while(i<3){
%>
<tr> 
<td colspan="3"> <img border="1" src="http://192.168.1.50:8100/ShowImage?photoid=<%=i%>"></td>
</tr>
<%
i++;
}
%>
</table>
</body>
</html>

注:此程序对于从数据库读取图片后写入文件请参考代码者留意

发表于 @ 2006年03月17日 11:26 PM | 评论 (0)

消除JDBC的瓶颈

摘要

    大部分的J2EE(Java 2 Platform, Enterprise Edition)和其它类型的Java应用都需要与数据库进行交互。与数据库进行交互需要反复地调用SQL语句、连接管理、事务生命周期、结果处理和异常处理。这些操作都是很常见的;不过这个重复的使用并不是必定需要的。在这篇文章中,我们将介绍一个灵活的架构,它可以解决与一个兼容JDBC的数据库的重复交互问题。

    最近在为公司开发一个小的J2EE应用时,我对执行和处理SQL调用的过程感到很麻烦。我认为在Java开发者中一定有人已经开发了一个架构来消除这个流程。不过,搜索诸如/"Java SQL framework" 或者 "JDBC [Java Database Connectivity] framework"等都没有得到满意的结果。

    问题的提出?

    在讲述一个解决方法之前,我们先将问题描述一下。如果你要通过一个JDBC数据源执行SQL指令时,你通常需要做些什么呢?

    1、建立一个SQL字符串

    2、得到一个连接

    3、得到一个预处理语句(prepared statement)

    4、将值组合到预处理语句中

    5、执行语句

    6、遍历结果集并且形成结果对象

    还有,你必须考虑那些不断产生的SQLExceptions;如果这些步骤出现不同的地方,SQLExecptions的开销就会复合在一起,因为你必须使用多个try/catch块。

    不过,如果我们仔细地观察一下这些步骤,就可以发现这个过程中有几个部分在执行期间是不变的:你通常都使用同一个方式来得到一个连接和一个预处理语句。组合预处理语句的方式通常也是一样的,而执行和处理查询则是特定的。你可以在六个步骤中提取中其中三个。即使在有点不同的步骤中,我们也可以在其中提取出公共的功能。但是我们应该怎样自动化及简化这个过程呢?

    查询架构

    我们首先定义一些方法的签名,这些方法是我们将要用来执行一个SQL语句的。要注意让它保持简单,只传送需要的变量,我们可以编写一些类似下面签名的方法:

CODE:


public Object[] executeQuery(String sql, Object[] pStmntValues,
ResultProcessor processor); 



    我们知道在执行期间有所不同的方面是SQL语句、预处理语句的值和结果集是如何分析的。很明显,sql参数指的是SQL语句。pStmntValues对象数据包含有必须插入到预处理语句中的值,而processor参数则是处理结果集并且返回结果对象的一个对象;我将在后面更详细地讨论这个对象。

    在这样一个方法签名中,我们就已经将每个JDBC数据库交互中三个不变的部分隔离开来。现在让我们讨论exeuteQuery()及其它支持的方法,它们都是SQLProcessor类的一部分:

CODE:


public class SQLProcessor {

public Object[] executeQuery(String sql, Object[] pStmntValues,
ResultProcessor processor) {

//Get a connection (assume it's part of a ConnectionManager class)
Connection conn = ConnectionManager.getConnection();

//Hand off our connection to the method that will actually execute
//the call
Object[] results = handleQuery(sql, pStmntValues, processor, conn);

//Close the connection
closeConn(conn);

//And return its results
return results;
}

protected Object[] handleQuery(String sql, Object[] pStmntValues,
ResultProcessor processor, Connection conn) {

//Get a prepared statement to use
PreparedStatement stmnt = null;

try {

//Get an actual prepared statement
stmnt = conn.prepareStatement(sql);

//Attempt to stuff this statement with the given values. If
//no values were given, then we can skip this step.
if(pStmntValues != null) {
PreparedStatementFactory.buildStatement(stmnt, pStmntValues);
}

//Attempt to execute the statement
ResultSet rs = stmnt.executeQuery();

//Get the results from this query
Object[] results = processor.process(rs);

//Close out the statement only. The connection will be closed by the
//caller.
closeStmnt(stmnt);

//Return the results
return results;

//Any SQL exceptions that occur should be recast to our runtime query
//exception and thrown from here
} catch(SQLException e) {
String message = "Could not perform the query for " + sql;

//Close out all resources on an exception
closeConn(conn);
closeStmnt(stmnt);

//And rethrow as our runtime exception
throw new DatabaseQueryException(message);
}
}
}
...
}
 



    在这些方法中,有两个部分是不清楚的:PreparedStatementFactory.buildStatement() 和 handleQuery()'s processor.process()方法调用。buildStatement()只是将参数对象数组中的每个对象放入到预处理语句中的相应位置。例如:

CODE:


...

//Loop through all objects of the values array, and set the value
//of the prepared statement using the value array index
for(int i = 0; i < values.length; i++) {

//If the object is our representation of a null value, then handle it separately
if(value instanceof NullSQLType) {
stmnt.setNull(i + 1, ((NullSQLType) value).getFieldType());
} else {
stmnt.setObject(i + 1, value);
}



    由于stmnt.setObject(int index, Object value)方法不可以接受一个null对象值,因此我们必须使用自己特殊的构造:NullSQLType类。NullSQLType表示一个null语句的占位符,并且包含有该字段的JDBC类型。当一个NullSQLType对象实例化时,它获得它将要代替的字段的SQL类型。如上所示,当预处理语句通过一个NullSQLType组合时,你可以使用NullSQLType的字段类型来告诉预处理语句该字段的JDBC类型。这就是说,你使用NullSQLType来表明正在使用一个null值来组合一个预处理语句,并且通过它存放该字段的JDBC类型。

    现在我已经解释了PreparedStatementFactory.buildStatement()的逻辑,我将解释另一个缺少的部分:processor.process()。processor是ResultProcessor类型,这是一个接口,它表示由查询结果集建立域对象的类。ResultProcessor包含有一个简单的方法,它返回结果对象的一个数组:

CODE:


public interface ResultProcessor {
public Object[] process(ResultSet rs) throws SQLException;



    一个典型的结果处理器遍历给出的结果集,并且由结果集合的行中形成域对象/对象结构。现在我将通过一个现实世界中的例子来综合讲述一下。

    查询例子

    你经常都需要利用一个用户的信息表由数据库中得到一个用户的对象,假设我们使用以下的USERS表:

CODE:


USERS table
Column Name Data Type 
ID NUMBER 
USERNAME VARCHAR 
F_NAME VARCHAR 
L_NAME VARCHAR 
EMAIL VARCHAR  



    并且假设我们拥有一个User对象,它的构造器是:

public User(int id, String userName, String firstName,
String lastName, String email) 

    如果我们没有使用这篇文章讲述的架构,我们将需要一个颇大的方法来处理由数据库中接收用户信息并且形成User对象。那么我们应该怎样利用我们的架构呢?

    首先,我们构造SQL语句:

CODE:


private static final String SQL_GET_USER = "SELECT * FROM USERS WHERE ID = ?"; 



    接着,我们形成ResultProcessor,我们将使用它来接受结果集并且形成一个User对象:

CODE:


public class UserResultProcessor implements ResultProcessor {

//Column definitions here (i.e., COLUMN_USERNAME, etc...)
..

public Object[] process(ResultSet rs) throws SQLException {

//Where we will collect all returned users
List users = new ArrayList();
User user = null;

//If there were results returned, then process them
while(rs.next()) {

user = new User(rs.getInt(COLUMN_ID), rs.getString(COLUMN_USERNAME),
rs.getString(COLUMN_FIRST_NAME), rs.getString(COLUMN_LAST_NAME),
rs.getString(COLUMN_EMAIL));

users.add(user);
}

return users.toArray(new User[users.size()]); 



    最后,我们将写一个方法来执行查询并且返回User对象:

CODE:


public User getUser(int userId) {

//Get a SQL processor and execute the query
SQLProcessor processor = new SQLProcessor();
Object[] users = processor.executeQuery(SQL_GET_USER_BY_ID,
new Object[] {new Integer(userId)},
new UserResultProcessor());

//And just return the first User object
return (User) users[0];



    这就是全部。我们只需要一个处理类和一个简单的方法,我们就可以无需进行直接的连接维护、语句和异常处理。此外,如果我们拥有另外一个查询由用户表中得到一行,例如通过用户名或者密码,我们可以重新使用UserResultProcessor。我们只需要插入一个不同的SQL语句,并且可以重新使用以前方法的用户处理器。由于返回行的元数据并不依赖查询,所以我们可以重新使用结果处理器。

    更新的架构

    那么数据库更新又如何呢?我们可以用类似的方法处理,只需要进行一些修改就可以了。首先,我们必须增加两个新的方法到SQLProcessor类。它们类似executeQuery()和handleQuery()方法,除了你无需处理结果集,你只需要将更新的行数作为调用的结果:

CODE:


public void executeUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor) {

//Get a connection
Connection conn = ConnectionManager.getConnection();

//Send it off to be executed
handleUpdate(sql, pStmntValues, processor, conn);

//Close the connection
closeConn(conn);
}

protected void handleUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor, Connection conn) {

//Get a prepared statement to use
PreparedStatement stmnt = null;

try {

//Get an actual prepared statement
stmnt = conn.prepareStatement(sql);

//Attempt to stuff this statement with the given values. If
//no values were given, then we can skip this step.
if(pStmntValues != null) {
PreparedStatementFactory.buildStatement(stmnt, pStmntValues);
}

//Attempt to execute the statement
int rows = stmnt.executeUpdate();

//Now hand off the number of rows updated to the processor
processor.process(rows);

//Close out the statement only. The connection will be closed by the
//caller.
closeStmnt(stmnt);

//Any SQL exceptions that occur should be recast to our runtime query
//exception and thrown from here
} catch(SQLException e) {
String message = "Could not perform the update for " + sql;

//Close out all resources on an exception
closeConn(conn);
closeStmnt(stmnt);

//And rethrow as our exception
throw new DatabaseUpdateException(message);
}
}
 



    这些方法和查询处理方法的区别仅在于它们是如何处理调用的结果:由于一个更新的操作只返回更新的行数,因此我们无需结果处理器。我们也可以忽略更新的行数,不过有时我们可能需要确认一个更新的产生。UpdateProcessor获得更新行的数据,并且可以对行的数目进行任何类型的确认或者记录:

CODE:


public interface UpdateProcessor {
public void process(int rows);



    如果一个更新的调用必须至少更新一行,这样实现UpdateProcessor的对象可以检查更新的行数,并且可以在没有行被更新的时候抛出一个特定的异常。或者,我们可能需要记录下更新的行数,初始化一个结果处理或者触发一个更新的事件。你可以将这些需求的代码放在你定义的UpdateProcessor中。你应该知道:各种可能的处理都是存在的,并没有任何的限制,可以很容易得集成到架构中。
更新的例子

    我将继续使用上面解释的User模型来讲述如何更新一个用户的信息:

    首先,构造SQL语句:

CODE:


private static final String SQL_UPDATE_USER = "UPDATE USERS SET USERNAME = ?, " +
"F_NAME = ?, " +
"L_NAME = ?, " +
"EMAIL = ? " +
"WHERE ID = ?";
 



    接着,构造UpdateProcessor,我们将用它来检验更新的行数,并且在没有行被更新的时候抛出一个异常:

CODE:


public class MandatoryUpdateProcessor implements UpdateProcessor {
public void process(int rows) {
if(rows < 1) {
String message = "There were no rows updated as a result of this operation.";
throw new IllegalStateException(message);
}
}



    最后就写编写执行更新的方法:

CODE:


public static void updateUser(User user) {

SQLProcessor sqlProcessor = new SQLProcessor();

//Use our get user SQL statement
sqlProcessor.executeUpdate(SQL_UPDATE_USER,
new Object[] {user.getUserName(),
user.getFirstName(),
user.getLastName(),
user.getEmail(),
new Integer(user.getId())},
new MandatoryUpdateProcessor()); 



    如前面的例子一样,我们无需直接处理SQLExceptions和Connections就执行了一个更新的操作。

    事务

    前面已经说过,我对其它的SQL架构实现都不满意,因为它们并不拥有预定义语句、独立的结果集处理或者可处理事务。我们已经通过buildStatement() 的方法解决了预处理语句的问题,还有不同的处理器(processors)已经将结果集的处理分离出来。不过还有一个问题,我们的架构如何处理事务呢?

    一个事务和一个独立SQL调用的区别只是在于在它的生命周期内,它都使用同一个连接,还有,自动提交标志也必须设置为off。因为我们必须有一个方法来指定一个事务已经开始,并且在何时结束。在整个事务的周期内,它都使用同一个连接,并且在事务结束的时候进行提交。

    要处理事务,我们可以重用SQLProcessor的很多方面。为什么将该类的executeUpdate() 和handleUpdate()独立开来呢,将它们结合为一个方法也很简单的。我这样做是为了将真正的SQL执行和连接管理独立开来。在建立事务系统时,我们必须在几个SQL执行期间对连接进行控制,这样做就方便多了。

    为了令事务工作,我们必须保持状态,特别是连接的状态。直到现在,SQLProcessor还是一个无状态的类。它缺乏成员变量。为了重用SQLProcessor,我们创建了一个事务封装类,它接收一个SQLProcessor并且透明地处理事务的生命周期。

    具体的代码是:

CODE:


public class SQLTransaction {
private SQLProcessor sqlProcessor;
private Connection conn;

//Assume constructor that initializes the connection and sets auto commit to false
...

public void executeUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor) {

//Try and get the results. If an update fails, then rollback
//the transaction and rethrow the exception.
try {
sqlProcessor.handleUpdate(sql, pStmntValues, processor, conn);

} catch(DatabaseUpdateException e) {
rollbackTransaction();
throw e;

}

public void commitTransaction() {

//Try to commit and release all resources
try {
conn.commit();
sqlProcessor.closeConn(conn);

//If something happens, then attempt a rollback and release resources
} catch(Exception e) {
rollbackTransaction();
throw new DatabaseUpdateException("Could not commit the current transaction.");
}
}

private void rollbackTransaction() {

//Try to rollback and release all resources
try {
conn.rollback();
conn.setAutoCommit(true);
sqlProcessor.closeConn(conn);


//If something happens, then just swallow it
} catch(SQLException e) {
sqlProcessor.closeConn(conn);
}
}



    SQLTransaction拥有许多新的方法,但是其中的大部分都是很简单的,并且只处理连接或者事务处理。在整个事务周期内,这个事务封装类只是在SQLProcessor中增加了一个简单的连接管理。当一个事务开始时,它接收一个新的连接,并且将其自动提交属性设置为false。其后的每个执行都是使用同一个连接(传送到SQLProcessor的handleUpdate()方法中),因此事务保持完整。

    只有当我们的持久性对象或者方法调用commitTransaction()时,事务才被提交,并且关闭连接。如果在执行期间发生了异常,SQLTransaction可以捕捉该异常,自动进行回滚,并且抛出异常。

    事务例子

    让我们来看一个简单的事务

CODE:


//Reuse the SQL_UPDATE_USER statement defined above

public static void updateUsers(User[] users) {

//Get our transaction
SQLTransaction trans = sqlProcessor.startTransaction();

//For each user, update it
User user = null;
for(int i = 0; i < users.length; i++) {
user = users[i];
trans.executeUpdate(SQL_UPDATE_USER,
new Object[] {user.getUserName(),
user.getFirstName(),
user.getLastName(),
user.getEmail(),
new Integer(user.getId())},
new MandatoryUpdateProcessor());
}

//Now commit the transaction
trans.commitTransaction();
}
 



    上面为我们展示了一个事务处理的例子,虽然简单,但我们可以看出它是如何工作的。如果在执行executeUpdate()方法调用时失败,这时将会回滚事务,并且抛出一个异常。调用这个方法的开发者从不需要担心事务的回滚或者连接是否已经关闭。这些都是在后台处理的。开发者只需要关心商业的逻辑。

    事务也可以很轻松地处理一个查询,不过这里我没有提及,因为事务通常都是由一系列的更新组成的。

    问题

    在我写这篇文章的时候,对于这个架构,我提出了一些疑问。这里我将这些问题提出来,因为你们可能也会碰到同样的问题。

    自定义连接

    如果每个事务使用的连接不一样时会如何?如果ConnectionManager需要一些变量来告诉它从哪个连接池得到连接?你可以很容易就将这些特性集合到这个架构中。executeQuery() 和 executeUpdate()方法(属于SQLProcessor和SQLTransaction类)将需要接收这些自定义的连接参数,并且将他们传送到ConnectionManager。要记得所有的连接管理都将在执行的方法中发生。

    此外,如果更面向对象化一点,连接制造者可以在初始化时传送到SQLProcessor中。然后,对于每个不同的连接制造者类型,你将需要一个SQLProcessor实例。根据你连接的可变性,这或许不是理想的做法。

    ResultProcessor返回类型

    为什么ResultProcessor接口指定了process()方法应该返回一个对象的数组?为什么不使用一个List?在我使用这个架构来开发的大部分应用中,SQL查询只返回一个对象。如果构造一个List,然后将一个对象加入其中,这样的开销较大,而返回一个对象的一个数组是比较简单的。不过,如果在你的应用中需要使用对象collections,那么返回一个List更好。

    SQLProcessor初始管理

    在这篇文章的例子中,对于必须执行一个SQL调用的每个方法,初始化一个SQLProcessor。由于SQLProcessors完全是没有状态的,所以在调用的方法中将processor独立出来是很有意义的。

    而对于SQLTransaction类,则是缺少状态的,因此它不能独立使用。我建议你为SQLProcessor类增加一个简单的方法,而不是学习如何初始化一个SQLTransaction,如下所示:

public SQLTransaction startTransaction() {
return new SQLTransaction(this);
}

    这样就会令全部的事务功能都在SQLProcessor类中访问到,并且限制了你必须知道的方法调用。

    数据库异常

    我使用了几种不同类型的数据库异常将全部可能在运行时发生的SQLExceptions封装起来。在我使用该架构的应用中,我发现将这些异常变成runtime exceptions更为方便,所以我使用了一个异常处理器。你可能认为这些异常应该声明,这样它们可以尽量在错误的发生点被处理。不过,这样就会令SQL异常处理的流程和以前的SQLExceptions一样,这种情况我们是尽量避免的。

    省心的JDBC programming 

    这篇文章提出的架构可以令查询、更新和事务执行的操作更加简单。在类似的SQL调用中,你只需要关注可重用的支持类中的一个方法。我的希望是该架构可以提高你进行JDBC编程的效率。

发表于 @ 2006年03月17日 11:08 PM | 评论 (0)

JDBC初级应用实例(二)[动态访问数据库]

    上面有一位朋友问了,如果在已经连结的情况下,知道当前连结的库的表的情况呢?
其实只你已经连结了,你就能知道这个库中所以情况而不仅仅上表的情况:

    有时(我到目前只见到过一次),我们对一种新的数据库根本不知道它的结构或者是
其中的内容,好坏么我们如何来获取数据库的情况呢?
    真实的例子是这样的,我的朋友的公司接到了一个单子,对方使用的数据库是叫什么
/"titanium/"的,说实话由于本人的孤陋寡闻,在此之前从来不知道还有这种数据库,更别说如何
访问了,现在朋友要看里面有什么/"东西/",当然是一筹莫展.所以只好找我.
    接到电话后,我先问他是什么平台上跑的,如果连结的,他说是在windows下可以建立
ODBC数据源,哈哈,就是说可以用java建立Connection了,OK
    只能建立一下Connection,那么就可以得到这个数据库的所有元信息:
    DatabaseMetadata dbmd = conn.getMetadata();然后你可以从这个对象获取以下信
息:
    getUrl();      //返回与这个数据库的连结的URL,当然是已知的,要不你怎么连上去
    getUserName(); //返回与这个数据库的连结的用户,同上
    isReadOnly();数据库是否为只读
    getDatabaseProduceName();//数据库产品名称
    getDatabaseProduceVersion();//版本号
    getDriverName();//驱动程序
    getDriverVersion();//驱动程序版本

    以上内容没有什么意义

    ResultSet getTables(String catalog, 
                String schemaPattern,
                String tableNamePattern,
                String[] types) 
    可以得到该库中/"表/"的所有情况,这里的表包括表,视图,系统表,临时空间,别名,同义词
    对于各参数:
    String catalog,表的目录,可能为null,/"null/"匹配所有
    String 

抱歉!评论已关闭.