优化
JDBC
编程
这是我根据
MS SQL SERVER 2000 JDBC DRIVER HELP
,并参考其它资料整理而成。
ms
的这个帮助文件实在有失大家风范,示例代码很
.....
有兴趣者可以去下载
http://download.microsoft.com/download/SQLSVR2000/jdbc/2000/NT45XP/EN-US/setup.exe
。由于本人水平有限,文中不当之处请大家批评指正。
1.
尽量减少对数据库元数据方法的使用
同样是产生一个
ResultSet
对象,
DatabaseMetaData
对象的方法比其它
JDBC
方法相对要慢,因此平繁使用该方法会降低系统的性能。
在程序中应当对产生的结果集信息进行高速缓存,比如将
getTypeInfo()
返回的结果集存入
Vector
或
Hashtable
中,这样可大大提高程序的效率。
2.
应避免的方法调用模式
在方法调用时应当尽量避免传入
null
做为参数,虽然有时能执行成功,但这对
DB Server
负担很重。其实在很多情况下所需的参数是已知的。比如:
//
这里略去了捕获违例代码(下同)。
DatabaseMetaData md=...;
ResultSet rs=md.getTables(null,null,"authors",null);//
取得
MS SQL SERVER pubs
数据库中
authors
表的信息
.
应当写成:
ResultSet rs=md.getTables("northwind","dbo","authors",new String[]{"TABLE"});
这样使程序更有效可靠。
3.
使用哑查询语句来取得表的相关特征信息
一个哑查询语句(
Dummy Query
,译为哑查询不知是否恰当,愿与大家探讨)不会产生有记录的结果集,比如:
select * from tableName where 1=0
,因为条件永不成立,
DB Server
不会执行这条语句。因此,在不需产生记录行的情况下,哑查询能极大地提高程序的执行效率。比如我们要了解一个表的有关列信息时,上面的语句比
select * from tableName
这个语句要高效得多,后者数据库服务器要检索所有的行并返回一个记录集,而前者不需要。针对这一问题,
JDBC
可以有以下两种方法:
case 1:
使用
getColumns()
方法
//getColumns()
是
DatabaseMetaData
的一个方法,其有关信息请查阅
JDK1.3
文档
ResultSet rs=md.getColumns("pubs","dbo","authors",...);//
返回一个有记录的结果集
while(rs.next())//
通过滚动结果集取得列名
System.out.println(rs.getString(4));
case 2:
使用
getMetaData()
方法
Statement stmt=conn.createStatement();
//
数据库服务器永远不会执行这条查询语句
ResultSet rs=stmt.executeQuery("select * from authors where 1=0");
ResultSetMetaData rsmd=rs.getMetaData();
int colCount=rsmd.getColumnCount();//
取得列数
for(int col=1;col<=colCount;col++)
System.out.println(rsmd.getColumnName(col));
//!
这里列的顺序是
select
后列出现的顺序,并不一定与表中列顺序对应
通过以上的分析,第二种方法应是我们的选择。
4.
关于存储过程的调用
由于所有的
JDBC
驱动总是将
SQL
语句作为字符串发送到数据库服务器,数据库服务器经过语法分析、参数类型验证,然后将参数转换成正确的数据类型再去执行。比如有这么一个存储过程:
CallableStatement cstmt=conn.prepareCall("{call getCustomerName(123)}");
//
获得指定
id
的客户的名字,输入参数,
id
是个正整数
ResultSet rs=cstmt.executeQuery();
在这里我们认为
123
是一个正整数,但实际
"call getCustomerName(123)"
作为字符串整个被发送到数据库服务器端,数据库服务器经过分析,离析出
"123"
将其转换为整数型值再做为参数送给存储过程执行。很明显,这样效率极低,因为我们把已知的东西仍要服务器去判断,这无疑额外加重了服务器的负担。做为优化也是我们常见的存储过程的调用方法应是:
CallableStatement cstmt=conn.prepareCall("call getCustomerName(?)");
cstmt.setLong(1,123);//
将值和类型信息编码后发送
ResultSet rs=cstmt.executeQuery();
//do something
5.
正确使用
Statement
和
PreparedStatement
对象及其
execute
方法
Statement
对象是为仅执行一次的查询语句优化而设计的,
PreparedStatement
对象是为两次或更多次执行同一查询语句而设计的。
PreparedStatement
对象第一次执行一个准备好的查询要花一定的代价,然而它带来的好处是为以后的查询加快了速度;因为
SQL
语句已经进行编译并放入高速缓存,你可以一直重复使用;想要改变查询条件获得不同的结果集只需用
setXXX
方法改变主机变量(
?
)的值就行了。
由于
PreparedStatement
及
CallableStatement
都是
Statement
的子类,所以它们都有
execute(String sql),executeQuery(String sql),executeUpdate(String sql),executeBatch()
方法。
execute(String sql)
方法返回一个
boolean
值,它执行任意复杂的
sql
语句,可以产生多个结果集。如果有结果产生返回
true
,如果没有结果集产生或仅是一个更新记数则返回
false
。它产生的结果集可以通过
getResultSet()
和
getMoreResults()
获得,更新记数可通过
getUpdateCount()
获得。显然
execute(String sql)
方法的使用要复杂一些,因此如果只是简单的查询或更新操作请使用
executeQuery(String sql)
和
executeUpdate(String sql)
方法。
executeUpdate(String sql)
能执行
INSERT
,
UPDATE
,
DELETE
语句,及
DDL
和
DML
命令(此时返回值为
0
)。
如果需要进行更多的更新操作,只需将这些更新命令打包后一起提交给数据库,数据库一次处理所有的请求,这比逐条提交要高效得多。例如:
//
保存当前提交模式
boolean commitState=conn.getAutoCommit();
//
关闭自动提交模式
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
//
逐条加入
stmt.addBatch("INSERT INTO employees VALUES (1000, 'Joe Jones')");
stmt.addBatch("INSERT INTO departments VALUES (260, 'Shoe')");
stmt.addBatch("INSERT INTO emp_dept VALUES (1000, 260)");
//
一次提交
int[] updateCounts = stmt.executeBatch();
conn.commit();//
使更新生效
conn.setAutoCommit(commitState);//
恢复原来的提交模式
PreparedStatement
和
CallalbeStatement
对象的使用基本与
Statement
一样,请参阅
JDBC2.1API
。
6.
正确的使用游标
JDBC2.1
核心
API
提供三种结果集类型:
forward-only, scroll-insensitive
和
scroll-sensitive
。
forward-only
:仅向前型。如果你仅需要前向顺序滚动结果集中的所有行,仅向前游标能提供极高的性能;然而它不能从第一行上直接滚动到最后一行,也不能从最后一行滚到第一行。
scroll-insensitive
:滚动不敏感型。对于要求较高处理级别的应用来说,滚动不敏感型结果集是一个理想的选择,它支持向前和向后的记录集滚动。滚动不敏感型结果集的第一次请求是从数据库服务端取得所有满足条件的行,然后将它存储在客户端,也就是说是一个包含数据的客户端静态视图;虽然以后的操作比较快,但数据库服务器处理第一次的请求非常慢,尤其是当返回的数据量比较大时。因此,如果返回的只是一行记录我们就不应使用这种游标,使用仅向前就满足要求了;相反,如果返回的记录非常多,也不推荐使用这种游标,因为这些数据都存放在内存里,大量的数据将很快使内存耗尽。有些滚动不敏感游标的实现是将数据缓存到数据库服务器的一个临时表中,以免占用过多的内存资源。
scroll-sensitive
:滚动敏感型,有时也叫键集驱动游标。它是在你的数据库上对满足条件的记录行做了一个标识,好像行的主键,当你滚动结果集的时候,只有有标识的数据才会返回。由于每次的请求都要产生一次网络连接,因此速度是很慢的。
7.
只返回需要的行或列
听了上周六范生对
Oracle
核心的剖析,我算是搞清楚了对表的查询或更新,数据库低层操作其实是对磁盘文件的
read or write
,而
I/O
操作数据量越大耗时越多,软盘的读写速度大家是有目共睹的。为了避免不必要的数据传输,请小心使用
select * from ...
这样的语句,如果只需要一列就没必要返回所有的列,特别是当你不需要的列中含有大数据类型(如
BINARY
,
BLOB
,
CLOB
)或者说数据量较大时,会影响系统性能。
8.
使用连接池
连接池对数据库访问性能的提高是非常显著地,因为创建和销毁一个连接的代价都非常昂贵。连接池实现了数据库连接的共享,一个连接对象可以被多个用户多次重复使用。由容器管理的连接池就像是一个租赁公司,谁要使用就租给他一个,用完后还给我,下次要用接着出租,这样就免去了每次请求都要造个新的,而用完后又把它扔了。
关于连接池技术较为复杂,不过你也完全可以写自己的连接池对象,如果你看了《
JAVA2
高级编程》。
[
参考资料
]
1.MS SQL SERVER 2000 JDBC DRIVER HELP
2.
《
JAVA2
高级编程》
3.
《
JAVA2
核心技术
II
》
4.
《
J2EE
构建企业系统专家级解决方案》
5.
《
JSP
高级编程》
6.SUN JDBC2.1 API