java调用存储过程.docx
- 文档编号:18240145
- 上传时间:2023-08-14
- 格式:DOCX
- 页数:13
- 大小:22.23KB
java调用存储过程.docx
《java调用存储过程.docx》由会员分享,可在线阅读,更多相关《java调用存储过程.docx(13页珍藏版)》请在冰点文库上搜索。
java调用存储过程
CallableStatement对象为所有的DBMS提供了一种以标准形式调用已储存过程的方法。
已储存过程储存在数据库中。
对已储存过程的调用是CallableStatement对象所含的内容。
这种调用是用一种换码语法来写的,有两种形式:
一种形式带结果参,另一种形式不带结果参数。
结果参数是一种输出(OUT)参数,是已储存过程的返回值。
两种形式都可带有数量可变的输入(IN参数)、输出(OUT参数)或输入和输出(INOUT参数)的参数。
问号将用作参数的占位符。
在JDBC中调用已储存过程的语法如下所示。
注意,方括号表示其间的内容是可选项;方括号本身并非语法的组成部份。
{call过程名[(?
?
...)]}
返回结果参数的过程的语法为:
{?
=call过程名[(?
?
...)]}
不带参数的已储存过程的语法类似:
{call过程名}
通常,创建CallableStatement对象的人应当知道所用的DBMS是支持已储存过程的,并且知道这些过程都是些什么。
然而,如果需要检查,多种DatabaseMetaData方法都可以提供这样的信息。
例如,如果DBMS支持已储存过程的调用,则supportsStoredProcedures方法将返回true,而getProcedures方法将返回对已储存过程的描述。
CallableStatement继承Statement的方法(它们用于处理一般的SQL语句),还继承了PreparedStatement的方法(它们用于处理IN参)。
CallableStatement中定义的所有方法都用于处理OUT参数或INOUT参数的输出部分:
注册OUT参数的JDBC类型(一般SQL类型)、从这些参数中检索结果,或者检查所返回的值是否为JDBCNULL。
1、创建CallableStatement对象
CallableStatement对象是用Connection方法prepareCall创建的。
下例创建CallableStatement的实例,其中含有对已储存过程getTestData调用。
该过程有两个变量,但不含结果参数:
CallableStatementcstmt=con.prepareCall("{callgetTestData(?
?
)}");
其中?
占位符为IN、OUT还是INOUT参数,取决于已储存过程getTestData。
2、IN和OUT参数
将IN参数传给CallableStatement对象是通过setXXX方法完成的。
该方法继承自PreparedStatement。
所传入参数的类型决定了所用的setXXX方法(例如,用setFloat来传入float值等)。
如果已储存过程返回OUT参数,则在执行CallableStatement对象以前必须先注册每个OUT参数的JDBC类型(这是必需的,因为某些DBMS要求JDBC类型)。
注册JDBC类型是用registerOutParameter方法来完成的。
语句执行完后,CallableStatement的getXXX方法将取回参数值。
正确的getXXX方法是为各参数所注册的JDBC类型所对应的Java类型。
换言之,registerOutParameter使用的是JDBC类型(因此它与数据库返回的JDBC类型匹配),而getXXX将之转换为Java类型。
作为示例,下述代码先注册OUT参数,执行由cstmt所调用的已储存过程,然后检索在OUT参数中返回的值。
方法getByte从第一个OUT参数中取出一个Java字节,而getBigDecimal从第二个OUT参数中取出一个BigDecimal对象(小数点后面带三位数):
CallableStatementcstmt=con.prepareCall("{callgetTestData(?
?
)}");
cstmt.registerOutParameter(1,java.sql.Types.TINYINT);
cstmt.registerOutParameter(2,java.sql.Types.DECIMAL,3);
cstmt.executeQuery();
bytex=cstmt.getByte
(1);
java.math.BigDecimaln=cstmt.getBigDecimal(2,3);
CallableStatement与ResultSet不同,它不提供用增量方式检索大OUT值的特殊机制。
3、INOUT参数
既支持输入又接受输出的参数(INOUT参数)除了调用registerOutParameter方法外,还要求调用适当的setXXX方法(该方法是从PreparedStatement继承来的)。
setXXX方法将参数值设置为输入参数,而registerOutParameter方法将它的JDBC类型注册为输出参数。
setXXX方法提供一个Java值,而驱动程序先把这个值转换为JDBC值,然后将它送到数据库中。
这种IN值的JDBC类型和提供给registerOutParameter方法的JDBC类型应该相同。
然后,要检索输出值,就要用对应的getXXX方法。
例如,Java类型为byte的参数应该使用方法setByte来赋输入值。
应该给registerOutParameter提供类型为TINYINT的JDBC类型,同时应使用getByte来检索输出值。
下例假设有一个已储存过程reviseTotal,其唯一参数是INOUT参数。
方法setByte把此参数设为25,驱动程序将把它作为JDBCTINYINT类型送到数据库中。
接着,registerOutParameter将该参数注册为JDBCTINYINT。
执行完该已储存过程后,将返回一个新的JDBCTINYINT值。
方法getByte将把这个新值作为Javabyte类型检索。
CallableStatementcstmt=con.prepareCall("{callreviseTotal(?
)}");
cstmt.setByte(1,25);
cstmt.registerOutParameter(1,java.sql.Types.TINYINT);
cstmt.executeUpdate();
bytex=cstmt.getByte
(1);
4、先检索结果,再检索OUT参数
由于某些DBMS的限制,为了实现最大的可移植性,建议先检索由执行CallableStatement对象所产生的结果,然后再用CallableStatement.getXXX方法来检索OUT参数。
如果CallableStatement对象返回多个ResultSet对象(通过调用execute方法),在检索OUT参数前应先检索所有的结果。
这种情况下,为确保对所有的结果都进行了访问,必须对Statement方法getResultSet、getUpdateCount和getMoreResults进行调用,直到不再有结果为止。
检索完所有的结果后,就可用CallableStatement.getXXX方法来检索OUT参数中的值。
5、检索作为OUT参数的NULL值
返回到OUT参数中的值可能会是JDBCNULL。
当出现这种情形时,将对JDBCNULL值进行转换以使getXXX方法所返回的值为null、0或false,这取决于getXXX方法类型。
对于ResultSet对象,要知道0或false是否源于JDBCNULL的唯一方法,是用方法wasNull进行检测。
如果getXXX方法读取的最后一个值是JDBCNULL,则该方法返回true,否则返回flase。
本文阐述了怎么使用dbms存储过程。
我阐述了使用存储过程的基本的和高级特性,比如返回resultset。
本文假设你对dbms和jdbc已经非常熟悉,也假设你能够毫无障碍地阅读其它语言写成的代码(即不是java的语言),但是,并不要求你有任何存储过程的编程经历。
存储过程是指保存在数据库并在数据库端执行的程序。
你可以使用特殊的语法在java类中调用存储过程。
在调用时,存储过程的名称及指定的参数通过jdbc连接发送给dbms,执行存储过程并通过连接(如果有)返回结果。
使用存储过程拥有和使用基于ejb或corba这样的应用服务器一样的好处。
区别是存储过程可以从很多流行的dbms中免费使用,而应用服务器大都非常昂贵。
这并不只是许可证费用的问题。
使用应用服务器所需要花费的管理、编写代码的费用,以及客户程序所增加的复杂性,都可以通过dbms中的存储过程所整个地替代。
你可以使用java,python,perl或c编写存储过程,但是通常使用你的dbms所指定的特定语言。
oracle使用pl/sql,postgresql使用pl/pgsql,db2使用proceduralsql。
这些语言都非常相似。
在它们之间移植存储过程并不比在sun的ejb规范不同实现版本之间移植sessionbean困难。
并且,存储过程是为嵌入sql所设计,这使得它们比java或c等语言更加友好地方式表达数据库的机制。
因为存储过程运行在dbms自身,这可以帮助减少应用程序中的等待时间。
不是在java代码中执行4个或5个sql语句,而只需要在服务器端执行1个存储过程。
网络上的数据往返次数的减少可以戏剧性地优化性能。
使用存储过程
简单的老的jdbc通过callablestatement类支持存储过程的调用。
该类实际上是preparedstatement的一个子类。
假设我们有一个poets数据库。
数据库中有一个设置诗人逝世年龄的存储过程。
下面是对老酒鬼dylanthomas(oldsoakdylanthomas,不指定是否有关典故、文化,请批评指正。
译注)进行调用的详细代码:
try{
intage=39;
stringpoetname="dylanthomas";
callablestatementproc=connection.preparecall("{callset_death_age(?
?
)}");
proc.setstring(1,poetname);
proc.setint(2,age);
cs.execute();
}catch(sqlexceptione){//....}
传给preparecall方法的字串是存储过程调用的书写规范。
它指定了存储过程的名称,?
代表了你需要指定的参数。
和jdbc集成是存储过程的一个很大的便利:
为了从应用中调用存储过程,不需要存根(stub)类或者配置文件,除了你的dbms的jdbc驱动程序外什么也不需要。
当这段代码执行时,数据库的存储过程就被调用。
我们没有去获取结果,因为该存储过程并不返回结果。
执行成功或失败将通过例外得知。
失败可能意味着调用存储过程时的失败(比如提供的一个参数的类型不正确),或者一个应用程序的失败(比如抛出一个例外指示在poets数据库中并不存在“dylanthomas”)
结合sql操作与存储过程
映射java对象到sql表中的行相当简单,但是通常需要执行几个sql语句;可能是一个select查找id,然后一个insert插入指定id的数据。
在高度规格化(符合更高的范式,译注)的数据库模式中,可能需要多个表的更新,因此需要更多的语句。
java代码会很快地膨胀,每一个语句的网络开销也迅速增加。
将这些sql语句转移到一个存储过程中将大大简化代码,仅涉及一次网络调用。
所有关联的sql操作都可以在数据库内部发生。
并且,存储过程语言,例如pl/sql,允许使用sql语法,这比java代码更加自然。
下面是我们早期的存储过程,使用oracle的pl/sql语言编写:
createprocedureset_death_age(poetvarchar2,poet_agenumber)
poet_idnumber;
beginselectidintopoet_idfrompoetswherename=poet;
insertintodeaths(mort_id,age)values(poet_id,poet_age);
endset_death_age;
很独特?
不。
我打赌你一定期待看到一个poets表上的update。
这也暗示了使用存储过程实现是多么容易的一件事情。
set_death_age几乎可以肯定是一个很烂的实现。
我们应该在poets表中添加一列来存储逝世年龄。
java代码中并不关心数据库模式是怎么实现的,因为它仅调用存储过程。
我们以后可以改变数据库模式以提高性能,但是我们不必修改我们代码。
下面是调用上面存储过程的java代码:
publicstaticvoidsetdeathage(poetdyingbard,intage)throwssqlexception{
connectioncon=null;
callablestatementproc=null;
try{
con=connectionpool.getconnection();
proc=con.preparecall("{callset_death_age(?
?
)}");
proc.setstring(1,dyingbard.getname());
proc.setint(2,age);
proc.execute();
}
finally{
try{proc.close();}
catch(sqlexceptione){}
con.close();
}
}
为了确保可维护性,建议使用像这儿这样的static方法。
这也使得调用存储过程的代码集中在一个简单的模版代码中。
如果你用到许多存储过程,就会发现仅需要拷贝、粘贴就可以创建新的方法。
因为代码的模版化,甚至也可以通过脚本自动生产调用存储过程的代码。
functions
存储过程可以有返回值,所以callablestatement类有类似getresultset这样的方法来获取返回值。
当存储过程返回一个值时,你必须使用registeroutparameter方法告诉jdbc驱动器该值的sql类型是什么。
你也必须调整存储过程调用来指示该过程返回一个值。
下面接着上面的例子。
这次我们查询dylanthomas逝世时的年龄。
这次的存储过程使用postgresql的pl/pgsql:
createfunctionsnuffed_it_when(varchar)returnsintegerdeclare
poet_idnumber;
poet_agenumber;
begin
--firstgettheidassociatedwiththepoet.
selectidintopoet_idfrompoetswherename=$1;
--getandreturntheage.
selectageintopoet_agefromdeathswheremort_id=poet_id;
returnage;
end;languagepl/pgsql;
另外,注意pl/pgsql参数名通过unix和dos脚本的$n语法引用。
同时,也注意嵌入的注释,这是和java代码相比的另一个优越性。
在java中写这样的注释当然是可以的,但是看起来很凌乱,并且和sql语句脱节,必须嵌入到javastring中。
下面是调用这个存储过程的java代码:
connection.setautocommit(false);
callablestatementproc=connection.preparecall("{?
=callsnuffed_it_when(?
)}");
proc.registeroutparameter(1,types.integer);
proc.setstring(2,poetname);
cs.execute();
intage=proc.getint
(2);
如果指定了错误的返回值类型会怎样?
那么,当调用存储过程时将抛出一个runtimeexception,正如你在resultset操作中使用了一个错误的类型所碰到的一样。
复杂的返回值
关于存储过程的知识,很多人好像就熟悉我们所讨论的这些。
如果这是存储过程的全部功能,那么存储过程就不是其它远程执行机制的替换方案了。
存储过程的功能比这强大得多。
当你执行一个sql查询时,dbms创建一个叫做cursor(游标)的数据库对象,用于在返回结果中迭代每一行。
resultset是当前时间点的游标的一个表示。
这就是为什么没有缓存或者特定数据库的支持,你只能在resultset中向前移动。
某些dbms允许从存储过程中返回游标的一个引用。
jdbc并不支持这个功能,但是oracle、postgresql和db2的jdbc驱动器都支持在resultset上打开到游标的指针(pointer)。
设想列出所有没有活到退休年龄的诗人,下面是完成这个功能的存储过程,返回一个打开的游标,同样也使用postgresql的pl/pgsql语言:
createprocedurelist_early_deaths()returnrefcursorasdeclare
toesuprefcursor;
begin
opentoesupforselectpoets.name,deaths.agefrompoets,deaths--allentriesindeathsareforpoets.--butthetablemightbecomegeneric.
wherepoets.id=deaths.mort_idanddeaths.age<60;
returntoesup;
end;languageplpgsql;
下面是调用该存储过程的java方法,将结果输出到printwriter:
printwriter:
staticvoidsendearlydeaths(printwriterout){
connectioncon=null;
callablestatementtoesup=null;
try{
con=connectionpool.getconnection();
//postgresqlneedsatransactiontodothis...con.
setautocommit(false);//setupthecall.
callablestatementtoesup=connection.preparecall("{?
=calllist_early_deaths()}");
toesup.registeroutparameter(1,types.other);
toesup.execute();
resultsetrs=(resultset)toesup.getobject
(1);
while(rs.next()){
stringname=rs.getstring
(1);
intage=rs.getint
(2);
out.println(name+"was"+age+"yearsold.");
}
rs.close();
}
catch(sqlexceptione){//weshouldprotectthesecalls.toesup.close();con.close();
}
}
因为jdbc并不直接支持从存储过程中返回游标,我们使用types.other来指示存储过程的返回类型,然后调用getobject()方法并对返回值进行强制类型转换。
这个调用存储过程的java方法是mapping的一个好例子。
mapping是对一个集上的操作进行抽象的方法。
不是在这个过程上返回一个集,我们可以把操作传送进去执行。
本例中,操作就是把resultset打印到一个输出流。
这是一个值得举例的很常用的例子,下面是调用同一个存储过程的另外一个方法实现:
publicclassprocesspoetdeaths{
publicabstractvoidsenddeath(stringname,intage);
}
staticvoidmapearlydeaths(processpoetdeathsmapper){
connectioncon=null;
callablestatementtoesup=null;
try{
con=connectionpool.getconnection();
con.setautocommit(false);
callablestatementtoesup=connection.preparecall("{?
=calllist_early_deaths()}");
toesup.registeroutparameter(1,types.other);
toesup.execute();
resultsetrs=(resultset)toesup.getobject
(1);
while(rs.next()){
stringname=rs.getstring
(1);
intage=rs.getint
(2);
mapper.senddeath(name,age);
}
rs.close();
}catch(sqlexceptione){//weshouldprotectthesecalls.toesup.close();
con.close();
}
}
这允许在resultset数据上执行任意的处理,而不需要改变或者复制获取resultset的方法:
staticvoidsendearlydeaths(finalprintwriterout){
processpoetdeathsmymapper=newprocesspoetdeaths(){
publicvoidsenddeath(stringname,intage){
out.println(name+"was"+age+"yearsold.");
}
};
mapearlydeaths(mymapper);
}
这个方法使用processpoetdeaths的一个匿名实例调用map
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- java 调用 存储 过程