站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Java 专题消除JDBC的瓶颈

消除JDBC的瓶颈

摘要: 大部分的J2EE(Java 2 Platform, Enterprise Edition)和其它类型的Java应用都需要与数据库进行交互

  摘要

  大部分的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语句的。要注意让它保持简单,只传送需要的变量,我们可以编写一些类似下面签名的方法:

<table width="100%" bgColor=#ffffff> <tr> <td>public Object[] executeQuery(String sql, Object[] pStmntValues,
ResultProcessor processor);</td></tr></table>
  我们知道在执行期间有所不同的方面是SQL语句、预处理语句的值和结果集是如何分析的。很明显,sql参数指的是SQL语句。pStmntValues对象数据包含有必须插入到预处理语句中的值,而processor参数则是处理结果集并且返回结果对象的一个对象;我将在后面更详细地讨论这个对象。

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

<table width="100%" bgColor=#ffffff> <tr> <td>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);
}
}
}
...
}
</td></tr></table>
  在这些方法中,有两个部分是不清楚的:PreparedStatementFactory.buildStatement() 和 handleQuery()'s processor.process()方法调用。buildStatement()只是将参数对象数组中的每个对象放入到预处理语句中的相应位置。例如:

<table width="100%" bgColor=#ffffff> <tr> <td>...

//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);
}
}</td></tr></table>
  由于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包含有一个简单的方法,它返回结果对象的一个数组:

<table width="100%" bgColor=#ffffff> <tr> <td>public interface ResultProcessor {
public Object[] process(ResultSet rs) throws SQLException;
}</td></tr></table>
  一个典型的结果处理器遍历给出的结果集,并且由结果集合的行中形成域对象/对象结构。现在我将通过一个现实世界中的例子来综合讲述一下。

  查询例子

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

<table width="100%" bgColor=#ffffff> <tr> <td>USERS table
Column Name Data Type
ID NUMBER
USERNAME VARCHAR
F_NAME VARCHAR
L_NAME VARCHAR
EMAIL VARCHAR </td></tr></table>
  并且假设我们拥有一个User对象,它的构造器是:

<table width="100%" bgColor=#ffffff> <tr> <td>public User(int id, String userName, String firstName,
String lastName, String email)</td></tr></table>
  如果我们没有使用这篇文章讲述的架构,我们将需要一个颇大的方法来处理由数据库中接收用户信息并且形成User对象。那么我们应该怎样利用我们的架构呢?

  首先,我们构造SQL语句:

<table width="100%" bgColor=#ffffff> <tr> <td>private static final String SQL_GET_USER = "SELECT * FROM USERS WHERE ID = ?";</td></tr></table>
  接着,我们形成ResultProcessor,我们将使用它来接受结果集并且形成一个User对象:

<table width="100%" bgColor=#ffffff> <tr> <td>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()]);</td></tr></table>
  最后,我们将写一个方法来执行查询并且返回User对象:

<table width="100%" bgColor=#ffffff> <tr> <td>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];
}</td></tr></table>
  这就是全部。我们只需要一个处理类和一个简单的方法,我们就可以无需进行直接的连接维护、语句和异常处理。此外,如果我们拥有另外一个查询由用户表中得到一行,例如通过用户名或者密码,我们可以重新使用UserResultProcessor。我们只需要插入一个不同的SQL语句,并且可以重新使用以前方法的用户处理器。由于返回行的元数据并不依赖查询,所以我们可以重新使用结果处理器。

  更新的架构

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

<table width="100%" bgColor=#ffffff> <tr> <td>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);
}
}
</td></tr></table>
  这些方法和查询处理方法的区别仅在于它们是如何处理调用的结果:由于一个更新的操作只返回更新的行数,因此我们无需结果处理器。我们也可以忽略更新的行数,不过有时我们可能需要确认一个更新的产生。UpdateProcessor获得更新行的数据,并且可以对行的数目进行任何类型的确认或者记录:

<table width="100%" bgColor=#ffffff> <tr> <td>public interface UpdateProcessor {
public void process(int rows);
}</td></tr></table>
  如果一个更新的调用必须至少更新一行,这样实现UpdateProcessor的对象可以检查更新的行数,并且可以在没有行被更新的时候抛出一个特定的异常。或者,我们可能需要记录下更新的行数,初始化一个结果处理或者触发一个更新的事件。你可以将这些需求的代码放在你定义的UpdateProcessor中。你应该知道:各种可能的处理都是存在的,并没有任何的限制,可以很容易得集成到架构中。



↑返回目录
前一篇: MVC减少编程复杂性
后一篇: 用CORBA创建Client/Server 程序