Java数据库连接(JDBC)被广泛用在Java应用程序中。在本篇文章中,我们将讨论如何测定JDBC的性能,如何判断JDBC子系统中的哪一部分需要进行优化。
核心的java.sql界面
我们的目的是提高应用程序的性能。一般情况下,我们需要对应用程序进行分析,找出其中的瓶颈。当然了,要对分布式应用程序进行有效的分析是比较困难的,I/O是分析的一个重点,这是由分布式应用程序的特点决定的,分布式应用程序中的线程需要花费大量的时间等待I/O操作。目前还不清楚线程因等待读、写操作而阻塞是瓶颈的一部分呢还是一个无关紧要的小问题。在进行分析时,有一个独立的通信系统测试标准是重要的。那么在测试JDBC子系统的性能时,我们应当测试哪些指标呢?
在java.sql软件包中,有三个接口组成了JDBC的核心:Connection、Statement和ResultSet。与数据库的正常交互包括下面的几部分:
?从数据库驱动程序中获得一个Connection对象。
?从Connection对象中获取能够执行指定的SQL语句的Statement对象
?如果SQL语句需要从数据库中读取数据,则使用Statement对象获取一个提供对数据库中的数据进行访问的ResultSet对象。
下面的例子通过访问指定数据库表的每行记录的所有域、将每行的数据存储到String []、并将所有的行放到一个向量中,演示了标准的数据库交互过程。
public static Vector getATable(String tablename, Connection Connection)
throws SQLException
{
String sqlQuery = "SELECT * FROM " + tablename;
Statement statement = Connection.createStatement();
ResultSet resultSet = statement.executeQuery(sqlQuery);
int numColumns = resultSet.getMetaData().getColumnCount();
String[] aRow;
Vector allRows = new Vector();
while(resultSet.next())
{
aRow = new String[numColumns];
for (int i = 0; i
file://ResultSet的访问是从1开始的,数组是从0开始的。
aRow[i] = resultSet.getString(i+1);
allRows.addElement(aRow);
}
return allRows;
}
在java.sql或其他的SDK中没有Connection、Statement和ResultSet这三个对象的具体实现,这些对象以及其他的JDBC接口都是由数据库驱动程序的厂商开发的,并被作为数据库驱动程序的一部分包括在驱动程序软件包中。如果要打印出Connection对象或使用的其他对象的类名,可能会看到类似XXXConnection、XXXStatement、XXXConnectionImpl、XXXStatementImpl等字符串,其中的XXX就是正在使用的数据库的名字,例如Oracle。
如果我们要测试例子中getATable()方法的JDBC的性能,可以简单地在该方法的开始处和末尾处添加System.currentTimeMillis(),二者之间的时间差就是getATable()方法执行所使用的时间。只要数据库的交互过程与其他过程没有搅和在一起,就可以使用这种方法测试一个方法的JDBC性能。但通常情况下,Java应用程序的的数据库交互过程分布在许多类的许多方法中,而且很难将数据库交互过程单独分离出来。那么在这种情况下我们应该如何测试数据库交互过程的性能呢?
一个理想的方法是在所有的JDBC类中都内置测量性能的能力,然后可以在需要对其性能进行监测时简单地打开监测功能就可以了。正常情况下,JDBC类没有提供这种能力,但我们可以使用具备这种功能的类来替换它们,我们替换类的目标是提供与Proxy非常相似的对象。
使用一个接口的专用封装对象封装该接口的对象是一种有多种用途的成熟技术,collection类同步的封装对象就是最著名的一个例子,但还有其他许多用途。SDK中甚至有一个专门在运行时才生成封装对象的类:java.lang.reflect.Proxy类。封装对象也被称作代理对象,如果在本篇文章中使用代理对象这个术语,会使对封装JDBC对象的解释更复杂,因此,在本篇文章中仍然会坚持使用封装类。
要在上述功能的基础上添加测试数据库交互过程的功能,还需要对应用程序的其他部分作一些改变,很明显的是,这样作需要一定的代价。
幸运的是,当一个框架象JDBC那样几乎完全采用接口来定义时,要用另外的实现替换其中的作一个类就相当简单了。我们可以使用一个封装类替换一个接口的任何一种实现,该封装类封装原有的类,并转发所有对原来类的方法的调用。在本篇文章中,我们可以使用一个封装类替换掉JDBC类,将我们监测JDBC性能的功能放置在封装类中,然后使监测功能随整个应用程序的执行而执行。
封装Connection类
我们将首先讨论Connection类的封装。下面的ConnectionWrapper类实现了Connection类,该类有一个Connection类的实例变量和使用构建器的参数初始化实例变量的构建器,大多数的Connection类的方法被简单地定义为将调用托付给实例变量:
package tuning.jdbc;
import java.sql.*;
import java.util.Map;
public class ConnectionWrapper implements Connection
{
protected Connection realConnection;
public Connection realConnection () {
return realConnection;
}
public ConnectionWrapper (Connection Connection) {
realConnection = Connection;
}
public void clearWarnings() throws SQLException {
realConnection.clearWarnings();
}
public void close() throws SQLException {
realConnection.close();
}
public boolean isClosed() throws SQLException {
return realConnection.isClosed();
}
public void commit() throws SQLException {
realConnection.commit();
}
...
我省略了大部分的方法,但它们都符合下面的的模板,在需要使用从数据库驱动程序中获取的Connection对象的地方,我们可以简单地使用ConnectionWrapper封装Connection对象,而使用ConnectionWrapper对象。无论在哪里获取了Connection对象,我们都需要在该处添加下面的二行代码:
Connection dbConnection = getConnectionFromDriver();
dbConnection = new ConnectionWrapper(dbConnection);
获得连接是该应用程序中唯一需要改变的部分,这要求发现所有获得一个Connection对象的调用,并对该调用进行编辑。然而,大多数的应用程序使用一个集中的代理类提供Connection对象,在这种情况下,在应用程序中使用ConnectionWrapper就非常简单了。该代理类需要频繁地访问一个Connection对象池,因此在将一个Connection对象释放回Connection对象池中时,还需要作一些额外的工作,因为Connection对象首先需要被解包,例如:
public static void releaseConnection(Connection conn)
{
if (conn instanceof ConnectionWrapper)
conn = ( (ConnectionWrapper) conn).realConnection();
...
}
我们还没有真正地完成ConnectionWrapper类,ConnectionWrapper类中有一些方法不能简单地托付,这些就是提供各种Statement对象的方法:
public Statement createStatement() throws SQLException {
return new StatementWrapper(realConnection.createStatement(), this);
}
public Statement createStatement(int resultSetType,
int resultSetConcurrency) throws SQLException {
return new StatementWrapper(
realConnection.createStatement(resultSetType,
resultSetConcurrency), this);
}
public CallableStatement prepareCall(String sql) throws SQLException {
return new CallableStatementWrapper(
realConnection.prepareCall(sql), this, sql);
}
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
return new CallableStatementWrapper(
realConnection.prepareCall(sql, resultSetType,
resultSetConcurrency), this, sql);
}
public PreparedStatement prepareStatement(String sql)
throws SQLException {
return new PreparedStatementWrapper(
realConnection.prepareStatement(sql), this, sql);
}
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
return new PreparedStatementWrapper(
realConnection.prepareStatement(sql, resultSetType,
resultSetConcurrency), this, sql);
}
如上所示,我们需要定义三种Statement封装类,另外,我们还需要为DatabaseMetaData定义一个封装类,该封装类必须是完备的,因为DatabaseMetaData能够返回用来创建DatabaseMetaData的Connection对象,因此我们需要确保Connection对象是经过封装过的,而不是我们没有封装过的Connection对象。
public DatabaseMetaData getMetaData() throws SQLException {
return new DatabaseMetaDataWrapper(
realConnection.getMetaData(), this);
}
封装statement类
Statement、PreparedStatement和CallableStatement这三个statement类的封装类相似:
public class StatementWrapper implements Statement
{
protected Statement realStatement;
protected ConnectionWrapper connectionParent;
public StatementWrapper(Statemen