ホームページ  >  記事  >  Java  >  Java でのストアド プロシージャの呼び出し (詳細)

Java でのストアド プロシージャの呼び出し (詳細)

黄舟
黄舟オリジナル
2017-02-06 13:15:161664ブラウズ

この記事では、DBMS ストアド プロシージャの使用方法について説明します。 ResultSet を返すなど、ストアド プロシージャを使用する基本機能と高度な機能について説明しました。この記事では、読者がすでに DBMS と JDBC に精通しており、他の言語 (つまり Java 以外の言語) で書かれたコードを何の障害もなく読めることを前提としています。ストアド プロシージャ プログラミングの経験があることが必要です。
ストアド プロシージャとは、データベースに保存され、データベース側で実行されるプログラムを指します。特別な構文を使用して、Java クラスからストアド プロシージャを呼び出すことができます。呼び出されると、ストアド プロシージャの名前と指定されたパラメータが JDBC 接続を通じて DBMS に送信され、ストアド プロシージャが実行され、接続を通じて結果が返されます (存在する場合)。
ストアド プロシージャの使用には、EJB または CORBA に基づくアプリケーション サーバーを使用するのと同じ利点があります。違いは、ストアド プロシージャは多くの一般的な DBMS から無料で入手できるのに対し、アプリケーション サーバーはほとんどが非常に高価であることです。ライセンス料だけの問題ではありません。アプリケーション サーバーを使用する際の管理コストとコーディング コスト、およびクライアント プログラムの複雑さはすべて、DBMS のストアド プロシージャに置き換えることができます。
ストアド プロシージャは Java、Python、Perl、または C で作成できますが、通常は DBMS で指定された特定の言語を使用します。 Oracle は PL/SQL を使用し、PostgreSQL は pl/pgsql を使用し、DB2 は手続き型 SQL を使用します。これらの言語はすべて非常に似ています。ストアド プロシージャ間での移植は、Sun の EJB 仕様の異なる実装間でセッション Bean を移植するのと同じくらい難しいことではありません。さらに、ストアド プロシージャは SQL を埋め込むように設計されているため、Java や C などの言語よりもデータベース メカニズムを表現しやすい方法になります。
ストアド プロシージャは DBMS 自体で実行されるため、アプリケーションでの待機時間を短縮できます。 Java コードで 4 つまたは 5 つの SQL ステートメントを実行する代わりに、サーバー側で実行する必要があるストアド プロシージャは 1 つだけです。ネットワーク上のデータの往復回数を減らすと、パフォーマンスが大幅に向上します。

ストアド プロシージャの使用

単純な古い JDBC は、CallableStatement クラスを介したストアド プロシージャの呼び出しをサポートしています。このクラスは実際には PreparedStatement のサブクラスです。詩人のデータベースがあるとします。データベースには詩人の死亡年齢を設定するストアド プロシージャがあります。以下は、オールド ソーク ディラン トーマスを呼び出すための詳細なコードです (オールド ソーク ディラン トーマス、暗示や文化に関連しているかどうかは特定しません。批判して修正してください。翻訳):

try{
       int age = 39;
      String poetName = "dylan thomas";
      CallableStatement proc = connection.prepareCall("{ call set_death_age(?, ?) }");
      proc.setString(1, poetName);
      proc.setInt(2, age);
      cs.execute();
}catch (SQLException e){ // ....}



The string returns to the prepareCall メソッドはストアド プロシージャの呼び出しの記述規則です。ストアド プロシージャの名前を指定します。指定する必要があるパラメータを表します。
JDBC との統合は、ストアド プロシージャにとって非常に便利です。アプリケーションからストアド プロシージャを呼び出すには、スタブ クラスや設定ファイルは必要ありません。DBMS 用の JDBC ドライバー以外は何も必要ありません。
このコードが実行されると、データベースのストアド プロシージャが呼び出されます。ストアド プロシージャが結果を返さないため、結果を取得できませんでした。実行の成功または失敗は例外によってわかります。失敗とは、ストアド プロシージャの呼び出し時の失敗 (間違った型のパラメータを指定した場合など)、またはアプリケーションの失敗 (詩データベースに "Dylan Thomas" が存在しないことを示す例外をスローした場合など) を意味する場合があります。

複合 SQL操作とストアド プロシージャ

Java オブジェクトを SQL テーブルの行にマッピングするのは非常に簡単ですが、通常は、ID を見つけるために SELECT を実行し、その後、指定された ID を持つデータを挿入するために INSERT を実行する必要があります。高度に正規化されたデータベース スキーマでは、複数のテーブルの更新が必要になる場合があり、そのためさらに多くのステートメントが必要になります。 Java コードは急速に増大する可能性があり、各ステートメントのネットワーク オーバーヘッドが急速に増加する可能性があります。
これらの SQL ステートメントをストアド プロシージャに移動すると、コードが大幅に簡素化され、ネットワーク呼び出しが 1 つだけ必要になります。関連するすべての SQL 操作はデータベース内で実行できます。また、PL/SQL などのストアド プロシージャ言語では、Java コードよりも自然な SQL 構文の使用が可能です。以下は、Oracle の PL/SQL 言語で書かれた初期のストアド プロシージャです。

create procedure set_death_age(poet VARCHAR2, poet_age NUMBER)
      poet_id NUMBER;
      begin SELECT id INTO poet_id FROM poets WHERE name = poet;
      INSERT INTO deaths (mort_id, age) VALUES (poet_id, poet_age);
end set_death_age;

とてもユニークですか?いいえ。詩人のテーブルに更新があるのを期待していたと思います。これは、ストアド プロシージャを使用した実装がいかに簡単であるかを示唆しています。 set_death_age はほぼ確実に悪い実装です。死亡年齢を保存する列を詩人のテーブルに追加する必要があります。 Java コードはストアド プロシージャを呼び出すだけなので、データベース スキーマがどのように実装されるかは関係ありません。後でデータベース スキーマを変更してパフォーマンスを向上させることはできますが、コードを変更する必要はありません。
以下は、上記のストアド プロシージャを呼び出す Java コードです:

public static void setDeathAge(Poet dyingBard, int age) throws SQLException{
      Connection con = null;
      CallableStatement proc = null;
      try {
           con = connectionPool.getConnection();
           proc = con.prepareCall("{ call set_death_age(?, ?) }");
           proc.setString(1, dyingBard.getName());
            proc.setInt(2, age);
            proc.execute();
}
     finally {  
           try { proc.close(); }
           catch (SQLException e) {}  
            con.close();
     }
}

为了确保可维护性,建议使用像这儿这样的static方法。这也使得调用存储过程的代码集中在一个简单的模版代码中。如果你用到许多存储过程,就会发现仅需要拷贝、粘贴就可以创建新的方法。因为代码的模版化,甚至也可以通过脚本自动生产调用存储过程的代码。 

Functions 

存储过程可以有返回值,所以CallableStatement类有类似getResultSet这样的方法来获取返回值。当存储过程返回一个值时,你必须使用registerOutParameter方法告诉JDBC驱动器该值的SQL类型是什么。你也必须调整存储过程调用来指示该过程返回一个值。 
下面接着上面的例子。这次我们查询Dylan Thomas逝世时的年龄。这次的存储过程使用PostgreSQL的pl/pgsql: 

create function snuffed_it_when (VARCHAR) returns integer 'declare
                 poet_id NUMBER;
                 poet_age NUMBER;
begin
--first get the id associated with the poet.
               SELECT id INTO poet_id FROM poets WHERE name = $1;
--get and return the age.
                 SELECT age INTO poet_age FROM deaths WHERE mort_id = poet_id;
return age;
end;' language 'pl/pgsql';

另外,注意pl/pgsql参数名通过Unix和DOS脚本的$n语法引用。同时,也注意嵌入的注释,这是和Java代码相比的另一个优越性。在Java中写这样的注释当然是可以的,但是看起来很凌乱,并且和SQL语句脱节,必须嵌入到Java String中。 
下面是调用这个存储过程的Java代码: 

connection.setAutoCommit(false);
CallableStatement proc = connection.prepareCall("{ ? = call snuffed_it_when(?) }");
proc.registerOutParameter(1, Types.INTEGER);
proc.setString(2, poetName);
cs.execute();
int age = proc.getInt(2);

如果指定了错误的返回值类型会怎样?那么,当调用存储过程时将抛出一个RuntimeException,正如你在ResultSet操作中使用了一个错误的类型所碰到的一样。 

复杂的返回值 

关于存储过程的知识,很多人好像就熟悉我们所讨论的这些。如果这是存储过程的全部功能,那么存储过程就不是其它远程执行机制的替换方案了。存储过程的功能比这强大得多。 
当你执行一个SQL查询时,DBMS创建一个叫做cursor(游标)的数据库对象,用于在返回结果中迭代每一行。ResultSet是当前时间点的游标的一个表示。这就是为什么没有缓存或者特定数据库的支持,你只能在ResultSet中向前移动。 
某些DBMS允许从存储过程中返回游标的一个引用。JDBC并不支持这个功能,但是Oracle、PostgreSQL和DB2的JDBC驱动器都支持在ResultSet上打开到游标的指针(pointer)。 
设想列出所有没有活到退休年龄的诗人,下面是完成这个功能的存储过程,返回一个打开的游标,同样也使用PostgreSQL的pl/pgsql语言: 

create procedure list_early_deaths () return refcursor as 'declare
      toesup refcursor;
begin
      open toesup for SELECT poets.name, deaths.age FROM poets, deaths -- all entries in deaths are for poets. -- but the table might become generic.
      WHERE poets.id = deaths.mort_id AND deaths.age < 60;
      return toesup;
end;&#39; language &#39;plpgsql&#39;;

下面是调用该存储过程的Java方法,将结果输出到PrintWriter: 
PrintWriter: 

static void sendEarlyDeaths(PrintWriter out){
      Connection con = null;
      CallableStatement toesUp = null;
      try {
          con = ConnectionPool.getConnection();
           // PostgreSQL needs a transaction to do this... con.
          setAutoCommit(false); // Setup the call.
          CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");
           toesUp.registerOutParameter(1, Types.OTHER);
           toesUp.execute();
           ResultSet rs = (ResultSet) toesUp.getObject(1);
           while (rs.next()) {
                  String name = rs.getString(1);
                  int age = rs.getInt(2);
                  out.println(name + " was " + age + " years old.");
            }
            rs.close();
         }  
       catch (SQLException e) { // We should protect these calls. toesUp.close(); con.close();
      }
}

因为JDBC并不直接支持从存储过程中返回游标,我们使用Types.OTHER来指示存储过程的返回类型,然后调用getObject()方法并对返回值进行强制类型转换。 
这个调用存储过程的Java方法是mapping的一个好例子。Mapping是对一个集上的操作进行抽象的方法。不是在这个过程上返回一个集,我们可以把操作传送进去执行。本例中,操作就是把ResultSet打印到一个输出流。这是一个值得举例的很常用的例子,下面是调用同一个存储过程的另外一个方法实现: 

public class ProcessPoetDeaths{
      public abstract void sendDeath(String name, int age);
}
      static void mapEarlyDeaths(ProcessPoetDeaths mapper){
      Connection con = null;
      CallableStatement toesUp = null;
      try {
           con = ConnectionPool.getConnection();
           con.setAutoCommit(false);
           CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");
            toesUp.registerOutParameter(1, Types.OTHER);
            toesUp.execute();
            ResultSet rs = (ResultSet) toesUp.getObject(1);
           while (rs.next()) {
           String name = rs.getString(1);
           int age = rs.getInt(2);
          mapper.sendDeath(name, age);
}
rs.close();
} catch (SQLException e) { // We should protect these calls. toesUp.close();
con.close();
}
}

这允许在ResultSet数据上执行任意的处理,而不需要改变或者复制获取ResultSet的方法: 

static void sendEarlyDeaths(final PrintWriter out){
            ProcessPoetDeaths myMapper = new ProcessPoetDeaths() {
                                public void sendDeath(String name, int age) {
                                                    out.println(name + " was " + age + " years old.");
                               }
           };
mapEarlyDeaths(myMapper);
}

这个方法使用ProcessPoetDeaths的一个匿名实例调用mapEarlyDeaths。该实例拥有sendDeath方法的一个实现,和我们上面的例子一样的方式把结果写入到输出流。当然,这个技巧并不是存储过程特有的,但是和存储过程中返回的ResultSet结合使用,是一个非常强大的工具。 

结论 

存储过程可以帮助你在代码中分离逻辑,这基本上总是有益的。这个分离的好处有: 
• 快速创建应用,使用和应用一起改变和改善的数据库模式。  
• 数据库模式可以在以后改变而不影响Java对象,当我们完成应用后,可以重新设计更好的模式。 
• 存储过程通过更好的SQL嵌入使得复杂的SQL更容易理解。 
• 编写存储过程比在Java中编写嵌入的SQL拥有更好的工具--大部分编辑器都提供语法高亮! 
• 存储过程可以在任何SQL命令行中测试,这使得调试更加容易。 

并不是所有的数据库都支持存储过程,但是存在许多很棒的实现,包括免费/开源的和非免费的,所以移植并不是一个问题。Oracle、PostgreSQL和DB2都有类似的存储过程语言,并且有在线的社区很好地支持。 
存储过程工具很多,有像TOAD或TORA这样的编辑器、调试器和IDE,提供了编写、维护PL/SQL或pl/pgsql的强大的环境。 
存储过程确实增加了你的代码的开销,但是它们和大多数的应用服务器相比,开销小得多。如果你的代码复杂到需要使用DBMS,我建议整个采用存储过程的方式。

以上就是在Java中调用存储过程(详细)的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。