封装ResultSet类
ResultSetWrapper类也主要包括托付方法:
public class ResultSetWrapper implements ResultSet
{
ResultSet realResultSet;
StatementWrapper parentStatement;
String sql;
public ResultSetWrapper(ResultSet resultSet, StatementWrapper statement, String sql) {
realResultSet = resultSet;
parentStatement = statement;
this.sql = sql;
}
public boolean absolute(int row) throws SQLException {
return realResultSet.absolute(row);
}
...
其中也有一些方法不是简单的托付方法,getStatement()方法返回生成ResultSet的statement对象,我们需要让它返回StatementWrapper对象:
public Statement getStatement() throws SQLException {
return parentStatement;
}
The getArray() methods need to return a wrapped Array object:
public Array getArray(int i) throws SQLException {
return new SQLArrayWrapper(realResultSet.getArray(i), parentStatement, sql);
}
public Array getArray(String colName) throws SQLException {
return new SQLArrayWrapper(realResultSet.getArray(colName), parentStatement, sql);
}
最后,我们需要添加测试过程。许多开发人员都错误地认为,不同的Statement.execute*()方法都会引起数据库交互过程带来的负担,对于数据库的更新和读取少量的数据库记录而言,这是正确的。如果读取的数据库记录的量较大,ResultSet.next()需要大量的时间从数据库中读取记录。如果读取的记录太多,ResultSet.next()调用所需要的时间就会多于SQL语句执行的时间。因此,测试ResultSet.next()调用的时间也就是理所当然的了。
public boolean next() throws SQLException {
Thread t = Thread.currentThread();
JDBCLogger.startLogSqlNext(t, sql);
boolean b = realResultSet.next();
JDBCLogger.endLogSqlNext(t, sql);
return b;
}
如果需要,还有一些ResultSet调用可以测量,例如previous()、insertRow()等,但大多数的应用程序只需要对next()进行测量。
JDBC封装类架构
上面讨论了需要封装的类,我没有明确地说明Array和DatabaseMetaData的封装类,但它们都比较简单,只需要返回ResultSetWrappers和ConnectionWrappers而不是ResultSets和Connections类。使用封装对象测试数据库交互过程性能的技术适用于JDBC 1、JDBC 2和未来的JDBC 3,它们在接口定义方面互不相同(因此需要不同的封装类。但我们可以用同一种方式创建所有不同版本下的封装类。
我没有讨论的是JDBCLogger,该类的一个简单的实现中不调用测试方法,但将不提供测试功能:
package tuning.jdbc;
public class JDBCLogger
{
public static void startLogSqlQuery(Thread t, String sql) {}
public static void endLogSqlQuery(Thread t, String sql) {}
public static void startLogSqlNext(Thread t, String sql) {}
public static void endLogSqlNext(Thread t, String sql) {}
}
一个更有用的定义是测试查询的时间。下面的方法记录查询开始时的时间,并在查询结束时得出使用的时间。由于假定在同一个线程中SQL查询不能递归(一般情况下都是这样的),下面的方法是相当简单的:
private static Hashtable QueryTime = new Hashtable();
public static void startLogSqlQuery(Thread t, String sql)
{
if (QueryTime.get(t) != null)
System.out.println("WARNING: overwriting sql query log time for " + sql);
QueryTime.put(t, new Long(System.currentTimeMillis()));
}
public static void endLogSqlQuery(Thread t, String sql)
{
long time = System.currentTimeMillis();
time -= ((Long) QueryTime.get(t)).longValue();
System.out.println("Time: " + time + " millis for SQL query " + sql);
QueryTime.remove(t);
}
使用JDBCLogger类中的这些方法的输出将如下所示:
Time: 53 millis for SQL query SELECT * FROM JACKTABL
对于每次查询执行来说,这将使我们能够精确地测试SQL查询所使用的时间,也能够计算出JDBCLogger类中所有查询所需要的时间。我经常测试的是最小、最大、平均、平均偏差等值,这些值在测试大规模的系统的性能时更有用。
使用JDBC封装类框架
我们已经介绍了非常有用的在应用程序的开发和布置阶段测试JDBC调用性能的方法。由于封装类比较简单,而且功能强大,又不需要对应用程序进行大量的修改,它们可以被保留在已经布置好的应用程序中,创建一个可配置的JDBCLogger类将使我们能够根据自己的需要开启或关闭测试功能。
在开发阶段,由于能够计算出累积的时间代价,我们能够利用这些类辨别出个别的需要较大时间代价的数据库交互过程和重复的数据库交互过程,哪个的时间代价更大。辨别出时间代价较大的数据库交互过程是我们改进应用程序性能的第一步。在开发阶段,这些封装类可以用来发现应用程序的理论性能和实际性能之间的差距,有助于我们分析为什么会有差距。
在利用这些类找出JDBC的性能瓶颈在哪里后,我们就可以对数据库的接口进行调整了。我将在以后的文章中继续讨论JDBC性能的技术。