JDBC之Statement,PreparedStatement,CallableStatement的区别

来源:互联网 发布:seo 优化 编辑:程序博客网 时间:2024/05/20 18:50

Statement 每次执行sql语句,数据库都要执行sql语句的编译 ,最好用于仅执行一次查询并返回结果的情形,效率高于PreparedStatement.

 

PreparedStatement是预编译的,使用PreparedStatement有几个好处

 a. 在执行可变参数的一条SQL时,PreparedStatement比Statement的效率高,因为DBMS预编译一条SQL当然会比多次编译一条SQL的效率要高。

 b. 安全性好,有效防止Sql注入等问题。

 c.  对于多次重复执行的语句,使用PreparedStament效率会更高一点,并且在这种情况下也比较适合使用batch;

 d.  代码的可读性和可维护性。

 

CallableStatement接口扩展 PreparedStatement,用来调用存储过程,它提供了对输出和输入/输出参数的支持。CallableStatement 接口还具有对 PreparedStatement 接口提供的输入参数的支持。

 

 

 

用法介绍

 

statement 1:

String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′";
stmt.executeUpdate(updateString);

preparedstatement 2:

PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
updateSales.setInt(1, 75);
updateSales.setString(2, "Colombian");
updateSales.executeUpdate();

片断2和片断1的区别在于,后者使用了PreparedStatement对象,而前者是普通的Statement对象。PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句,而不必先编译。当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。
这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出它预编译的优越性。
5.执行许多SQL语句的JDBC程序产生大量的Statement和PreparedStatement对象。通常认为PreparedStatement对象比Statement对象更有效,特别是如果带有不同参数的同一SQL语句被多次执行的时候。PreparedStatement对象允许数据库预编译SQL语句,这样在随后的运行中可以节省时间并增加代码的可读性。

然而,在Oracle环境中,开发人员实际上有更大的灵活性。当使用Statement或PreparedStatement对象时,Oracle数据库会缓存SQL语句以便以后使用。在一些情况下,由于驱动器自身需要额外的处理和在Java应用程序和Oracle服务器间增加的网络活动,执行PreparedStatement对象实际上会花更长的时间。

然而,除了缓冲的问题之外,至少还有一个更好的原因使我们在企业应用程序中更喜欢使用PreparedStatement对象,那就是安全性。传递给PreparedStatement对象的参数可以被强制进行类型转换,使开发人员可以确保在插入或查询数据时与底层的数据库格式匹配。

当处理公共Web站点上的用户传来的数据的时候,安全性的问题就变得极为重要。传递给PreparedStatement的字符串参数会自动被驱动器忽略。最简单的情况下,这就意味着当你的程序试着将字符串“D'Angelo”插入到VARCHAR2中时,该语句将不会识别第一个“,”,从而导致悲惨的失败。几乎很少有必要创建你自己的字符串忽略代码。

在Web环境中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。特别是在公共Web站点上,在没有首先通过PreparedStatement对象处理的情况下,所有的用户输入都不应该传递给SQL语句。此外,在用户有机会修改SQL语句的地方,如HTML的隐藏区域或一个查询字符串上,SQL语句都不应该被显示出来。

 

 

 

 

 

callablestatement 使用方法

在 JDBC 中调用已储存过程的语法如下所示。

{call 过程名[(?, ?, ...)]}


  返回结果参数的过程的语法为:

{? = call 过程名[(?, ?, ...)]}


  不带参数的已储存过程的语法类似:

{call 过程名}


  通常,创建 CallableStatement 对象的人应当知道所用的 DBMS 是支持已储存过程的,并且知道这些过程都是些什么。然而,如果需要检查,多种DatabaseMetaData 方法都可以提供这样的信息。例如,如果 DBMS 支持已储存过程的调用,则supportsStoredProcedures 方法将返回 true,而getProcedures 方法将返回对已储存过程的描述。CallableStatement 继承 Statement 的方法(它们用于处理一般的 SQL 语句),还继承了 PreparedStatement 的方法(它们用于处理 IN 参)。

  CallableStatement 中定义的所有方法都用于处理 OUT 参数或 INOUT 参数的输出部分:注册 OUT 参数的 JDBC 类型(一般 SQL 类型)、从这些参数中检索结果,或者检查所返回的值是否为 JDBC NULL。

  1、创建 CallableStatement 对象 

  CallableStatement 对象是用 Connection 方法 prepareCall 创建的。下例创建 CallableStatement 的实例,其中含有对已储存过程 getTestData 调用。该过程有两个变量,但不含结果参数:

CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");


  其中?占位符为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 对象(小数点后面带三位数):

 

CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3);
cstmt.executeQuery();
byte x = cstmt.getByte(1);
java.math.BigDecimal n = cstmt.getBigDecimal(2, 3);

 

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,驱动程序将把它作为 JDBC TINYINT 类型送到数据库中。接着,registerOutParameter 将该参数注册为 JDBC TINYINT。执行完该已储存过程后,将返回一个新的 JDBC TINYINT 值。方法 getByte 将把这个新值作为 Java byte 类型检索。

CallableStatement cstmt = con.prepareCall("{call reviseTotal(?)}");
cstmt.setByte(1, 25);
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.executeUpdate();

byte x = cstmt.getByte(1);


CallableStatement 对象为所有的 DBMS 提供了一种以标准形式调用已储存过程的方法。已储存过程储存在数据库中。对已储存过程的调用是 CallableStatement 对象所含的内容。这种调用是用一种换码语法来写的,有两种形式:一种形式带结果参数,另一种形式不带结果参数(有关换码语法的信息,参见第 4 节“语句”)。结果参数是一种输出 (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 类型)、从这些参数中检索结果,或者检查所返回的值是否为 JDBC NULL。 


7.1.1 创建 CallableStatement 对象 
CallableStatement 对象是用 Connection 方法 prepareCall 创建的。下例创建 CallableStatement 的实例,其中含有对已储存过程 getTestData 调用。该过程有两个变量,但不含结果参数: 

CallableStatement cstmt = con.prepareCall( 
"{call getTestData(?, ?)}"); 

其中 ? 占位符为 IN、 OUT 还是 INOUT 参数,取决于已储存过程 getTestData。 


7.1.2 IN 和 OUT 参数 
将 IN 参数传给 CallableStatement 对象是通过 setXXX 方法完成的。该方法继承自 PreparedStatement。所传入参数的类型决定了所用的 setXXX 方法(例如,用 setFloat 来传入 float 值等)。 

如果已储存过程返回 OUT 参数,则在执行 CallableStatement 对象以前必须先注册每个 OUT 参数的 JDBC 类型(这是必需的,因为某些 DBMS 要求 JDBC 类型)。注册 JDBC 类型是用 registerOutParameter 方法来完成的。语句执行完后,CallableStatement 的 getXXX 方法将取回参数值。正确的 getXXX 方法是为各参数所注册的 JDBC 类型所对应的 Java 类型(从 JDBC 类型到 Java 类型的标准映射见 8.6.1 节中的表)。换言之, registerOutParameter 使用的是 JDBC 类型(因此它与数据库返回的 JDBC 类型匹配),而 getXXX 将之转换为 Java 类型。 

作为示例,下述代码先注册 OUT 参数,执行由 cstmt 所调用的已储存过程,然后检索在 OUT 参数中返回的值。方法 getByte 从第一个 OUT 参数中取出一个 Java 字节,而 getBigDecimal 从第二个 OUT 参数中取出一个 BigDecimal 对象(小数点后面带三位数): 

CallableStatement cstmt = con.prepareCall( 
"{call getTestData(?, ?)}"); 
cstmt.registerOutParameter(1, java.sql.Types.TINYINT); 
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3); 
cstmt.executeQuery(); 
byte x = cstmt.getByte(1); 
java.math.BigDecimal n = cstmt.getBigDecimal(2, 3); 

CallableStatement 与 ResultSet 不同,它不提供用增量方式检索大 OUT 值的特殊机制。 


7.1.3 INOUT 参数 
既支持输入又接受输出的参数(INOUT 参数)除了调用 registerOutParameter 方法外,还要求调用适当的 setXXX 方法(该方法是从 PreparedStatement 继承来的)。setXXX 方法将参数值设置为输入参数,而 registerOutParameter 方法将它的 JDBC 类型注册为输出参数。setXXX 方法提供一个 Java 值,而驱动程序先把这个值转换为 JDBC 值,然后将它送到数据库中。 

这种 IN 值的 JDBC 类型和提供给 registerOutParameter 方法的 JDBC 类型应该相同。然后,要检索输出值,就要用对应的 getXXX 方法。例如,Java 类型为 byte 的参数应该使用方法 setByte 来赋输入值。应该给 registerOutParameter 提供类型为 TINYINT 的 JDBC 类型,同时应使用 getByte 来检索输出值 (第 8 节“JDBC 和 Java 类型之间的映射”将给出详细信息和类型映射表)。 

下例假设有一个已储存过程 reviseTotal,其唯一参数是 INOUT 参数。方法 setByte 把此参数设为 25,驱动程序将把它作为 JDBC TINYINT 类型送到数据库中。接着,registerOutParameter 将该参数注册为 JDBC TINYINT。执行完该已储存过程后,将返回一个新的 JDBC TINYINT 值。方法 getByte 将把这个新值作为 Java byte 类型检索。 

CallableStatement cstmt = con.prepareCall( 
"{call reviseTotal(?)}"); 
cstmt.setByte(1, 25); 
cstmt.registerOutParameter(1, java.sql.Types.TINYINT); 
cstmt.executeUpdate(); 
byte x = cstmt.getByte(1); 

7.1.4 先检索结果,再检索 OUT 参数 
由于某些 DBMS 的限制,为了实现最大的可移植性,建议先检索由执行 CallableStatement 对象所产生的结果,然后再用 CallableStatement.getXXX 方法来检索 OUT 参数。 

如果 CallableStatement 对象返回多个 ResultSet 对象(通过调用 execute 方法),在检索 OUT 参数前应先检索所有的结果。这种情况下,为确保对所有的结果都进行了访问,必须对 Statement 方法 getResultSet、getUpdateCount 和 getMoreResults 进行调用,直到不再有结果为止。 

检索完所有的结果后,就可用 CallableStatement.getXXX 方法来检索 OUT 参数中的值。 


7.1.5 检索作为 OUT 参数的 NULL 值 
返回到 OUT 参数中的值可能会是 JDBC NULL。当出现这种情形时,将对 JDBC NULL 值进行转换以使 getXXX 方法所返回的值为 null、0 或 false,这取决于 getXXX 方法类型。对于 ResultSet 对象,要知道 0 或 false 是否源于 JDBC NULL 的唯一方法,是用方法 wasNull 进行检测。如果 getXXX 方法读取的最后一个值是 JDBC NULL,则该方法返回 true,否则返回 flase。第 5 节“ResultSet”将给出详细信息。


什么是存储过程?

就是带有逻辑的sql语句,由于增删改查没有带逻辑,使用存储过程可以判断,循环。

如何创建存储过程?

[html] view plain copy
  1. CREATE PROCEDURE pro_test()           --存储过程名称(参数列表)  
  2. BEGIN               
  3.     SELECT * FROM employee;  
  4. END             
  5.   
  6. CALL pro_test();          -- 使用CALL执行 存储过程名称(参数);  

带有输入参数的存储过程:

[html] view plain copy
  1. CREATE PROCEDURE pro_findById(IN eid INT)  -- IN: 输入参数  
  2. BEGIN  
  3.     SELECT * FROM employee WHERE id=eid;  
  4. END   
  5.   
  6. CALL pro_findById(4);  

带有输出参数的存储过程:

[html] view plain copy
  1. CREATE PROCEDURE pro_testOut(OUT str VARCHAR(20))  -- OUT:输出参数  
  2. BEGIN  
  3.         -- 给参数赋值  
  4.     SET str='helljava';  
  5. END  

删除存储过程:

[html] view plain copy
  1. DROP PROCEDURE pro_testOut;  

带有条件判断的存储过程:

需求:输入一个整数,如果1,则返回“张三”,如果2,返回“李四”,如果3,返回“王五”。其他数字,返回“错误输入”;

[html] view plain copy
  1. CREATE PROCEDURE pro_testIf(IN num INT,OUT str VARCHAR(20))  
  2. BEGIN  
  3.     IF num=1 THEN  
  4.         SET str='张三 ';  
  5.     ELSEIF num=2 THEN  
  6.         SET str='李四 ';  
  7.     ELSEIF num=3 THEN  
  8.         SET str='王五 ';  
  9.     ELSE  
  10.         SET str='输入错误';  
  11.     END IF;  
  12. END  
  13.   
  14. CALL pro_testIf(4,@str);  
  15. SELECT @str;--查看输出的值  

带有循环功能的存储过程:

需求:输入一个整数,求和。

[html] view plain copy
  1. CREATE PROCEDURE pro_testWhile(IN num INT,OUT result INT)  
  2. BEGIN  
  3.     -- 定义一个局部变量  
  4.     DECLARE i INT DEFAULT 1;  
  5.     DECLARE vsum INT DEFAULT 0;  
  6.     WHILE i<=num DO  
  7.           SET vsum = vsum+i;  
  8.           SET i=i+1;  
  9.     END WHILE;  
  10.     SET result=vsum;  
  11. END   
  12. <p>CALLpro_testWhile(100,@result);</p><p>SELECT @result;</p>  
语法形式就这样了

总结:

参数:

IN:   表示输入参数,可以携带数据带存储过程中

OUT: 表示输出参数,可以从存储过程中返回结果

INOUT: 表示输入输出参数,既可以输入功能,也可以输出功能

---------------------------------------------------------

一个代码是带有输入参数的存储过程例子:

封装了JDBC的工具类:

[html] view plain copy
  1. public class JdbcUtil {  
  2.     private static String url = null;  
  3.     private static String user = null;  
  4.     private static String password = null;  
  5.     private static String driverClass = null;  
  6.       
  7.     /**  
  8.      * 静态代码块中(只加载一次)  
  9.      */  
  10.     static{  
  11.         try {  
  12.             //读取db.properties文件  
  13.             Properties props = new Properties();  
  14.             /**  
  15.              *  . 代表java命令运行的目录  
  16.              *  在java项目下,. java命令的运行目录从项目的根目录开始  
  17.              *  在web项目下,  . java命令的而运行目录从tomcat/bin目录开始  
  18.              *  所以不能使用点.  
  19.              */  
  20.             //FileInputStream in = new FileInputStream("./src/db.properties");  
  21.               
  22.             /**  
  23.              * 使用类路径的读取方式  
  24.              *  / : 斜杠表示classpath的根目录  
  25.              *     在java项目下,classpath的根目录从bin目录开始  
  26.              *     在web项目下,classpath的根目录从WEB-INF/classes目录开始  
  27.              */  
  28.             InputStream in = JdbcUtil.class.getResourceAsStream("/db.properties");  
  29.               
  30.             //加载文件  
  31.             props.load(in);  
  32.             //读取信息  
  33.             url = props.getProperty("url");  
  34.             user = props.getProperty("user");  
  35.             password = props.getProperty("password");  
  36.             driverClass = props.getProperty("driverClass");  
  37.               
  38.               
  39.             //注册驱动程序  
  40.             Class.forName(driverClass);  
  41.         } catch (Exception e) {  
  42.             e.printStackTrace();  
  43.             System.out.println("驱程程序注册出错");  
  44.         }  
  45.     }  
  46.   
  47.     /**  
  48.      * 抽取获取连接对象的方法  
  49.      */  
  50.     public static Connection getConnection(){  
  51.         try {  
  52.             Connection conn = DriverManager.getConnection(url, user, password);  
  53.             return conn;  
  54.         } catch (SQLException e) {  
  55.             e.printStackTrace();  
  56.             throw new RuntimeException(e);  
  57.         }  
  58.     }  
  59.       
  60.       
  61.     /**  
  62.      * 释放资源的方法  
  63.      */  
  64.     public static void close(Connection conn,Statement stmt){  
  65.         if(stmt!=null){  
  66.             try {  
  67.                 stmt.close();  
  68.             } catch (SQLException e) {  
  69.                 e.printStackTrace();  
  70.                 throw new RuntimeException(e);  
  71.             }  
  72.         }  
  73.         if(conn!=null){  
  74.             try {  
  75.                 conn.close();  
  76.             } catch (SQLException e) {  
  77.                 e.printStackTrace();  
  78.                 throw new RuntimeException(e);  
  79.             }  
  80.         }  
  81.     }  
  82.       
  83.     public static void close(Connection conn,Statement stmt,ResultSet rs){  
  84.         if(rs!=null)  
  85.             try {  
  86.                 rs.close();  
  87.             } catch (SQLException e1) {  
  88.                 e1.printStackTrace();  
  89.                 throw new RuntimeException(e1);  
  90.             }  
  91.         if(stmt!=null){  
  92.             try {  
  93.                 stmt.close();  
  94.             } catch (SQLException e) {  
  95.                 e.printStackTrace();  
  96.                 throw new RuntimeException(e);  
  97.             }  
  98.         }  
  99.         if(conn!=null){  
  100.             try {  
  101.                 conn.close();  
  102.             } catch (SQLException e) {  
  103.                 e.printStackTrace();  
  104.                 throw new RuntimeException(e);  
  105.             }  
  106.         }  
  107.     }  
  108. }  

这个是主要的实现类:

[html] view plain copy
  1. @Test  
  2. public void test1(){  
  3.     Connection conn = null;  
  4.     CallableStatement stmt = null;  
  5.     ResultSet rs = null;  
  6.     try {  
  7.         //获取连接  
  8.         conn = JdbcUtil.getConnection();  
  9.               
  10.         //准备sql  
  11.         String sql = "CALL pro_findById(?)"; //可以执行预编译的sql  
  12.               
  13.         //预编译  
  14.         stmt = conn.prepareCall(sql);  
  15.               
  16.         //设置输入参数  
  17.         stmt.setInt(1, 1);  
  18.               
  19.         //发送参数,所有调用存储过程的sql语句都是使用executeQuery方法执行!!!  
  20.         rs = stmt.executeQuery();  
  21.               
  22.         //遍历结果  
  23.         while(rs.next()){  
  24.             int id = rs.getInt("id");  
  25.             String name = rs.getString("name");  
  26.             String gender = rs.getString("gender");  
  27.             System.out.println(id+","+name+","+gender);  
  28.         }  
  29.               
  30.     } catch (Exception e) {  
  31.         e.printStackTrace();  
  32.         throw new RuntimeException(e);  
  33.     } finally {  
  34.         //rs,stmt,conn按顺序关了  
  35.                 JdbcUtil.close(conn, stmt ,rs);  
  36.        }  
  37. }  
一个带有输出参数的存储过程:

[html] view plain copy
  1. @Test  
  2. public void test2(){  
  3.     Connection conn = null;  
  4.     CallableStatement stmt = null;  
  5.     ResultSet rs = null;  
  6.     try {  
  7.         //获取连接  
  8.         conn = JdbcUtil.getConnection();  
  9.         //准备sql  
  10.         String sql = "CALL pro_test2(?,?)"; //第一个?是输入参数,第二个?是输出参数  
  11.               
  12.         //预编译  
  13.         stmt = conn.prepareCall(sql);  
  14.               
  15.         //设置输入参数  
  16.         stmt.setInt(1, 3);  
  17.         //设置输出参数(注册输出参数)  
  18.         /**  
  19.          * 参数一: 参数位置  
  20.         * 参数二: 存储过程中的输出参数的jdbc类型    VARCHAR(20)  
  21.             */  
  22.         stmt.registerOutParameter(2, java.sql.Types.VARCHAR);  
  23.               
  24.         //发送参数,执行  
  25.         stmt.executeQuery(); //结果不是返回到结果集中,而是返回到输出参数中  
  26.               
  27.         //得到输出参数的值  
  28.         /**  
  29.          * 索引值: 预编译sql中的输出参数的位置  
  30.          */  
  31.         String result = stmt.getString(2); //getXX方法专门用于获取存储过程中的输出参数  
  32.               
  33.         System.out.println(result);  
  34.   
  35.     } catch (Exception e) {  
  36.         e.printStackTrace();  
  37.         throw new RuntimeException(e);  
  38.     } finally {  
  39.         JdbcUtil.close(conn, stmt ,rs);  
  40.     }  
  41. }  
结果:

ls

存储过程的优点:

执行效率很快(因为在数据库服务端执行的)。

缺点:

移植性差(不同数据库的存储过程是不可移植的)。


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 类型)、从这些参数中检索结果,或者检查所返回的值是否为 JDBC NULL。

1、创建 CallableStatement 对象

  CallableStatement 对象是用 Connection 方法 prepareCall 创建的。下例创建 CallableStatement 的实例,其中含有对已储存过程 getTestData 调用。该过程有两个变量,但不含结果参数:

CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");

  其中?占位符为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 对象(小数点后面带三位数):

CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");

cstmt.registerOutParameter(1, java.sql.Types.TINYINT);

cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3);

cstmt.executeQuery();

byte x = cstmt.getByte(1);

java.math.BigDecimal n = 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,驱动程序将把它作为 JDBC TINYINT 类型送到数据库中。接着,registerOutParameter 将该参数注册为 JDBC TINYINT。执行完该已储存过程后,将返回一个新的 JDBC TINYINT 值。方法 getByte 将把这个新值作为 Java byte 类型检索。

CallableStatement cstmt = con.prepareCall("{call reviseTotal(?)}");

cstmt.setByte(1, 25);

cstmt.registerOutParameter(1, java.sql.Types.TINYINT);

cstmt.executeUpdate();

byte x = 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 参数中的值可能会是JDBC NULL。当出现这种情形时,将对 JDBC NULL 值进行转换以使 getXXX 方法所返回的值为 null、0 或 false,这取决于getXXX 方法类型。对于 ResultSet 对象,要知道0或false是否源于JDBCNULL的唯一方法,是用方法wasNull进行检测。如果 getXXX 方法读取的最后一个值是 JDBC NULL,则该方法返回 true,否则返回 flase。

 

注:对于多行的返回,则必须通过强制转型为ResultSet进行返回,此时将返回指向多行游标的指针。如

Conncetion con = getConnection();

CallableStatement cs = null;

ResultSet rs = null;

 

try{

    cs = con.prepareCall("{?=call getCallfuc(?,?)}");

    cs.registerOutParameter(1,Types.OTHER);

    cs.setInt(2,0);

    cs.setInt(3,0);

  

    cs.execute();

    rs = (ResultSet)cs.getObject(1);

    while(rs.next()) {

       ....... 

   }

}catch(Exception ex){}


最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解。所以便把JDBC 这个东东翻出来,老调重弹,好好总结一番,作为自己的笔记,也是给读者一个参考~~~

      本文主要通过 使用JDBC创建存储过程 和使用JDBC调用存储过程两部分 阐述JDBC 对存储过程的支持。本文将在Oracle数据库下创建一个可以表示岗位信息的基本表Jobs为例, 然后通过存储过程对这个Jobs表进行各种操作。表JOBS的建表语句如下:

[sql] view plain copy
  1. -- Create table  
  2. create table JOBS  
  3. (  
  4.   job_id     VARCHAR2(10) not null,  
  5.   job_title  VARCHAR2(35),  
  6.   min_salary NUMBER(6),  
  7.   max_salary NUMBER(6)  
  8. );  
  9.   
  10. -- Add comments to the table   
  11. comment on table JOBS  
  12.   is '岗位信息表';  
  13. -- Add comments to the columns   
  14. comment on column JOBS.job_id  
  15.   is 'Job Id';  
  16. comment on column JOBS.job_title  
  17.   is '岗位名称';  
  18. comment on column JOBS.min_salary  
  19.   is '最小薪酬';  
  20. comment on column JOBS.max_salary  
  21.   is '最大薪酬';  
  22. -- Create/Recreate primary, unique and foreign key constraints   
  23. alter table JOBS  
  24.   add constraint PK_JOB_ID primary key (JOB_ID);  

JDBC创建存储过程

使用数据库操作数据库需要三个步骤: 执行 创建存储过程语句 --> 编译存储过程---> 调用存储过程

比如我们创建一个向表Jobs添加记录的存储过程,并且调用它,在数据库上要执行下列代码:

[sql] view plain copy
  1. --1.创建存储过程  
  2.   
  3. CREATE OR REPLACE PROCEDURE insert_jobs_proc(  
  4.     input_job_id IN VARCHAR2,  
  5.         input_job_title IN VARCHAR2,  
  6.         input_min_salary IN NUMBER,  
  7.         input_max_salary IN NUMBER) AS  
  8. BEGIN  
  9.     INSERT INTO jobs(job_id,job_title,Min_Salary,max_salary)VALUES(input_job_id,input_job_title,input_min_salary,input_max_salary);  
  10. END insert_jobs_proc;  
  11.   
  12. --2.编译存储过程  
  13. COMPILE;  
  14.   
  15. --3.使用存储过程  
  16. CALL insert_jobs_proc('AD_PRES','President',20080,40000);  

由于上述的代码本质上来说就是SQL代码,可以使用JDBC逐步执行上述的SQL代码即可(不过使用JDBC创建不需要调用compile进行编译,JDBC会自动让数据库编译):

[java] view plain copy
  1. public static void inTest(){  
  2.       
  3.     Connection connection = null;  
  4.     Statement statement = null;  
  5.     ResultSet resultSet = null;  
  6.       
  7.     try {  
  8.           
  9.         Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();  
  10.           
  11.         Driver driver = DriverManager.getDriver(URL);  
  12.         Properties props = new Properties();  
  13.         props.put("user", USER_NAME);  
  14.         props.put("password", PASSWORD);  
  15.           
  16.         connection = driver.connect(URL, props);  
  17.           
  18.         //获得Statement对象,这里使用了事务机制,如果创建存储过程语句失败或者是执行compile失败,回退  
  19.         connection.setAutoCommit(false);  
  20.         statement = connection.createStatement();  
  21.         String procedureString = "CREATE OR REPLACE PROCEDURE insert_jobs_proc("  
  22.                                      +"input_job_id IN VARCHAR2,"  
  23.                                   +"input_job_title IN VARCHAR2,"  
  24.                                   +"input_min_salary IN NUMBER,"  
  25.                                   +"input_max_salary IN NUMBER) AS "  
  26.                                      +"BEGIN "  
  27.                                   +"INSERT INTO jobs(job_id,job_title,Min_Salary,max_salary)VALUES(input_job_id,input_job_title,input_min_salary,input_max_salary); "  
  28.                                      +"END insert_jobs_proc;";  
  29.         //1 创建存储过程,JDBC 数据库会编译存储过程  
  30.         statement.execute(procedureString);  
  31.         //成功则提交  
  32.         connection.commit();  
  33.         //2.调用  
  34.         CallableStatement callableStatement = connection.prepareCall("CALL insert_jobs_proc(?,?,?,?)");  
  35.         //设置IN参数  
  36.         callableStatement.setString(1"AD_PRESS");  
  37.         callableStatement.setString(2"President");  
  38.         callableStatement.setBigDecimal(3new BigDecimal(20080));  
  39.         callableStatement.setBigDecimal(4new BigDecimal(40000));  
  40.           
  41.         callableStatement.execute();  
  42.         connection.commit();  
  43.           
  44.     } catch (ClassNotFoundException e) {  
  45.         System.out.println("加载Oracle类失败!");  
  46.         e.printStackTrace();  
  47.     } catch (SQLException e) {  
  48.         try {  
  49.             connection.rollback();  
  50.         } catch (SQLException e1) {  
  51.             e1.printStackTrace();  
  52.         }  
  53.         e.printStackTrace();  
  54.     } catch (InstantiationException e) {  
  55.         e.printStackTrace();  
  56.     } catch (IllegalAccessException e) {  
  57.         e.printStackTrace();  
  58.     }finally{  
  59.             //使用完成后管理链接,释放资源,释放顺序应该是: ResultSet ->Statement ->Connection  
  60.               
  61.             try {  
  62.                 statement.close();  
  63.             } catch (SQLException e) {  
  64.                 e.printStackTrace();  
  65.             }  
  66.               
  67.             try {  
  68.                 connection.close();  
  69.             } catch (SQLException e) {  
  70.                 e.printStackTrace();  
  71.             }  
  72.     }  
  73. }  


JDBC调用存储过程

使用JDBC调用存储过程的基本格式为:

CALL PROCEDURE_NAME(parameter1,parameter2,paramter3.....)

这里参数有三种不同的形式 :in 类型、out类型还有 in 和out的混合类型:

IN 类型:此类型是用于参数从外部传递给存储过程使用;

OUT类型:此类型是存储过程执行过程中的返回值;

IN、OUT混合类型:此类型是参数传入,然后返回。

以下分四种参数类型创建不同的存储过程,然后通过JDBC调用:

只有输入IN参数,没有输出OUT参数

 上面演示的存储过程  insert_jobs_proc 就是只有IN 参数传入的例子,请读者看上述的 例子。

既有输入IN参数,也有输出OUT参数,输出是简单值(非列表)

创建一个存储过程  get_job_min_salary_proc,传入特定岗位的job_id,返回输出此岗位的最小薪酬min_salary,对应的SQL语句如下:

[sql] view plain copy
  1. CREATE OR REPLACE PROCEDURE get_job_min_salary_proc(  
  2.     input_job_id IN VARCHAR2,  
  3.         output_salary OUT number) AS   
  4. BEGIN  
  5.     SELECT min_salary INTO output_salary FROM jobs WHERE job_id = input_job_id;  
  6. END   get_job_min_salary_proc;  

在JDBC中调用如下:

[java] view plain copy
  1. /* 
  2.  * 有IN 类型的参数输入 和Out类型的参数输出   
  3.  */  
  4.     public static void inOutTest(){  
  5.         Connection connection = null;  
  6.         Statement statement = null;  
  7.         ResultSet resultSet = null;  
  8.         try {  
  9.               
  10.             Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();  
  11.               
  12.             Driver driver = DriverManager.getDriver(URL);  
  13.             Properties props = new Properties();  
  14.             props.put("user", USER_NAME);  
  15.             props.put("password", PASSWORD);  
  16.               
  17.             connection = driver.connect(URL, props);  
  18.               
  19.             //获得Statement对象,这里使用了事务机制,如果创建存储过程语句失败或者是执行compile失败,回退  
  20.             connection.setAutoCommit(false);  
  21.             statement = connection.createStatement();  
  22.             String procedureString = "CREATE OR REPLACE PROCEDURE get_job_min_salary_proc("  
  23.                                       +"input_job_id IN VARCHAR2,"  
  24.                                       +"output_salary OUT number) AS "  
  25.                                       +"BEGIN "  
  26.                                       +"SELECT min_salary INTO output_salary FROM jobs WHERE job_id = input_job_id; "  
  27.                                       +"END   get_job_min_salary_proc;";  
  28.             //1 创建存储过程,JDBC 数据库会编译存储过程  
  29.             statement.execute(procedureString);  
  30.             //成功则提交  
  31.             connection.commit();  
  32.             //2.创建callableStatement  
  33.             CallableStatement callableStatement = connection.prepareCall("CALL get_job_min_salary_proc(?,?)");  
  34.             //3,设置in参数  
  35.             callableStatement.setString(1"AD_PRES");  
  36.             //4.注册输出参数  
  37.             callableStatement.registerOutParameter(2, Types.NUMERIC);  
  38.             //5.执行语句  
  39.             callableStatement.execute();  
  40.               
  41.             BigDecimal salary = callableStatement.getBigDecimal(2);  
  42.             System.out.println(salary);  
  43.               
  44.         } catch (ClassNotFoundException e) {  
  45.             System.out.println("加载Oracle类失败!");  
  46.             e.printStackTrace();  
  47.         } catch (SQLException e) {  
  48.             try {  
  49.                 connection.rollback();  
  50.             } catch (SQLException e1) {  
  51.                 e1.printStackTrace();  
  52.             }  
  53.             e.printStackTrace();  
  54.         } catch (InstantiationException e) {  
  55.             e.printStackTrace();  
  56.         } catch (IllegalAccessException e) {  
  57.             e.printStackTrace();  
  58.         }finally{  
  59.                 //使用完成后管理链接,释放资源,释放顺序应该是: ResultSet ->Statement ->Connection  
  60.                   
  61.                 try {  
  62.                     statement.close();  
  63.                 } catch (SQLException e) {  
  64.                     e.printStackTrace();  
  65.                 }  
  66.                   
  67.                 try {  
  68.                     connection.close();  
  69.                 } catch (SQLException e) {  
  70.                     e.printStackTrace();  
  71.                 }  
  72.         }  
  73.     }  
  74.       


既有输入IN参数,也有输出OUT参数,输出是列表

创建一个存储过程 get_min_greater_proc,输入参数 最小薪酬,返回jobs表里最小薪酬不小于此参数的岗位集合。

对应的SQL语句如下:

[sql] view plain copy
  1. --创建一个包,自定义一个数据类型 my_cursor  
  2. CREATE OR REPLACE PACKAGE my_package_cursor IS  
  3.  TYPE my_cursor IS REF CURSOR;  
  4. END  my_package_cursor;  
  5.   
  6. --创建 存储过程,通过传入最小薪酬,返回JOBs表内不小于最小薪酬的岗位集合  
  7. CREATE OR REPLACE PROCEDURE get_min_greater_proc(  
  8.      input_min_salary IN NUMBER,  
  9.      setResult OUT my_package_cursor.my_cursor) AS  
  10. BEGIN   
  11.     OPEN    setResult FOR   
  12.      SELECT * FROM jobs WHERE min_salary >= input_min_salary;  
  13. END     get_min_greater_proc;   

JDBC调用代码如下:

[java] view plain copy
  1. /* 
  2.  * 有IN 类型的参数输入 和Out类型的集合输出 
  3.  */  
  4.     public static void inOutResultSetTest(){  
  5.         Connection connection = null;  
  6.         Statement statement = null;  
  7.         ResultSet resultSet = null;  
  8.         try {  
  9.               
  10.             Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();  
  11.               
  12.             Driver driver = DriverManager.getDriver(URL);  
  13.             Properties props = new Properties();  
  14.             props.put("user", USER_NAME);  
  15.             props.put("password", PASSWORD);  
  16.             connection = driver.connect(URL, props);  
  17.             //1.创建callableStatement  
  18.             CallableStatement callableStatement = connection.prepareCall("CALL get_min_greater_proc(?,?)");  
  19.             //2,设置in参数  
  20.             callableStatement.setBigDecimal(1new BigDecimal(20000));  
  21.             //3.注册输出参数  
  22.             callableStatement.registerOutParameter(2, OracleTypes.CURSOR);  
  23.             //4.执行语句  
  24.             callableStatement.execute();  
  25.             //返回的是结果集  
  26.             resultSet = (ResultSet)callableStatement.getObject(2);  
  27.               
  28.         } catch (ClassNotFoundException e) {  
  29.             System.out.println("加载Oracle类失败!");  
  30.             e.printStackTrace();  
  31.         } catch (SQLException e) {  
  32.             try {  
  33.                 connection.rollback();  
  34.             } catch (SQLException e1) {  
  35.                 e1.printStackTrace();  
  36.             }  
  37.             e.printStackTrace();  
  38.         } catch (InstantiationException e) {  
  39.             e.printStackTrace();  
  40.         } catch (IllegalAccessException e) {  
  41.             e.printStackTrace();  
  42.         }finally{  
  43.                 //使用完成后管理链接,释放资源,释放顺序应该是: ResultSet ->Statement ->Connection  
  44.                   
  45.                 try {  
  46.                     statement.close();  
  47.                 } catch (SQLException e) {  
  48.                     e.printStackTrace();  
  49.                 }  
  50.                   
  51.                 try {  
  52.                     connection.close();  
  53.                 } catch (SQLException e) {  
  54.                     e.printStackTrace();  
  55.                 }  
  56.         }  
  57.     }  

输入输出参数是同一个(IN OUT)

创建一个存储过程 get_job_info ,传入job_id 返回 job_id ,返回的job_id 是输入的job_id 和对应的job_title 拼接而成。

[sql] view plain copy
  1. --创建存储过程 传入job_id 返回job_id  
  2. CREATE OR REPLACE PROCEDURE get_job_info(  
  3.      io_job_id IN OUT VARCHAR2) AS  
  4. BEGIN   
  5.          SELECT job_id ||job_title INTO io_job_id FROM jobs WHERE job_id =io_job_id ;  
  6. END     get_job_info;   

对应的JDBC代码如下:

[java] view plain copy
  1. //1.创建callableStatement  
  2.    CallableStatement callableStatement = connection.prepareCall("CALL get_job_info(?)");  
  3.    //2,设置in参数  
  4.    callableStatement.setString(1"AD_PRES");  
  5.    //3.注册输出参数  
  6.    callableStatement.registerOutParameter(1, Types.VARCHAR);  
  7.    //4.执行语句  
  8.    callableStatement.execute();  
  9.    //返回结果  
  10.    String jobId = callableStatement.getString(1);  
  11.    System.out.println(jobId);  



阅读全文
1 0
原创粉丝点击