优化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
