Prosedur tersimpan merujuk kepada program yang disimpan dalam pangkalan data dan dilaksanakan di bahagian pangkalan data. Anda boleh memanggil prosedur tersimpan daripada kelas Java menggunakan sintaks khas. Apabila dipanggil, nama prosedur tersimpan dan parameter yang ditentukan dihantar ke DBMS melalui sambungan JDBC, prosedur tersimpan dilaksanakan dan hasilnya dikembalikan melalui sambungan (jika ada).
Menggunakan prosedur tersimpan mempunyai faedah yang sama seperti menggunakan pelayan aplikasi berdasarkan EJB atau CORBA. Perbezaannya ialah prosedur tersimpan tersedia secara percuma daripada banyak DBMS yang popular, manakala pelayan aplikasi kebanyakannya sangat mahal. Ia bukan sahaja mengenai bayaran lesen. Kos pengurusan dan pengekodan menggunakan pelayan aplikasi, serta kerumitan tambahan program klien, semuanya boleh digantikan dengan prosedur tersimpan dalam DBMS.
Anda boleh menulis prosedur tersimpan dalam Java, Python, Perl atau C, tetapi secara amnya menggunakan bahasa khusus yang ditentukan oleh DBMS anda. Oracle menggunakan PL/SQL, PostgreSQL menggunakan pl/pgsql, dan DB2 menggunakan Procedural SQL. Bahasa-bahasa ini semuanya sangat serupa. Memindahkan prosedur tersimpan di antara mereka tidak lebih sukar daripada mengalihkan Biji Sesi antara pelaksanaan berbeza spesifikasi EJB Sun. Selain itu, prosedur tersimpan direka untuk membenamkan SQL, yang menjadikannya cara yang lebih mesra untuk menyatakan mekanisme pangkalan data daripada bahasa seperti Java atau C.
Oleh kerana prosedur tersimpan dijalankan dalam DBMS itu sendiri, ini boleh membantu mengurangkan masa menunggu dalam aplikasi. Daripada melaksanakan 4 atau 5 pernyataan SQL dalam kod Java, hanya 1 prosedur tersimpan perlu dilaksanakan pada bahagian pelayan. Mengurangkan bilangan perjalanan pergi balik data pada rangkaian boleh meningkatkan prestasi secara mendadak.
Menggunakan prosedur tersimpan
JDBC lama yang ringkas menyokong penggunaan prosedur tersimpan melalui kelas CallableStatement. Kelas ini sebenarnya adalah subkelas PreparedStatement. Katakan kita mempunyai pangkalan data penyair. Terdapat prosedur tersimpan dalam pangkalan data yang menetapkan umur kematian penyair. Berikut ialah kod terperinci untuk memanggil Dylan Thomas rendam lama:
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){ // ....}
Rentetan yang dihantar kepada kaedah prepareCall ialah prosedur tersimpan Konvensyen penulisan untuk panggilan. Ia menyatakan nama prosedur yang disimpan,? Mewakili parameter yang perlu anda tentukan. Penyepaduan dengan JDBC adalah kemudahan yang hebat untuk prosedur tersimpan: untuk memanggil prosedur tersimpan daripada aplikasi, tiada kelas stub atau fail konfigurasi diperlukan, tiada apa yang diperlukan kecuali pemacu JDBC DBMS anda.
Apabila kod ini dilaksanakan, prosedur tersimpan pangkalan data dipanggil. Kami tidak mendapat keputusan kerana prosedur yang disimpan tidak mengembalikan hasil. Kejayaan atau kegagalan pelaksanaan akan diketahui melalui pengecualian. Kegagalan mungkin bermakna kegagalan dalam memanggil prosedur yang disimpan (seperti membekalkan parameter jenis yang salah), atau kegagalan aplikasi (seperti membuang pengecualian yang menunjukkan bahawa "Dylan Thomas" tidak wujud dalam pangkalan data puisi)
Gabungkan operasi SQL dengan prosedur tersimpan
Memetakan objek Java ke baris dalam jadual SQL agak mudah, tetapi biasanya memerlukan melaksanakan beberapa pernyataan SQL, mungkin SELECT untuk mencari ID INSERT memasukkan data dengan ID yang ditentukan. Dalam skema pangkalan data yang sangat normal, kemas kini kepada berbilang jadual mungkin diperlukan, justeru memerlukan lebih banyak pernyataan. Kod Java boleh berkembang dengan cepat, dan overhed rangkaian setiap penyata boleh meningkat dengan cepat.
Mengalih pernyataan SQL ini ke dalam prosedur tersimpan akan sangat memudahkan kod, melibatkan hanya satu panggilan rangkaian. Semua operasi SQL yang berkaitan boleh berlaku di dalam pangkalan data. Juga, bahasa prosedur tersimpan, seperti PL/SQL, membenarkan penggunaan sintaks SQL, yang lebih semula jadi daripada kod Java. Berikut ialah prosedur tersimpan awal kami, yang ditulis dalam bahasa PL/SQL Oracle:
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;
Adakah ia unik? Tidak. Saya yakin anda menjangkakan untuk melihat KEMASKINI pada meja penyair. Ini juga membayangkan betapa mudahnya untuk melaksanakan menggunakan prosedur tersimpan. set_death_age hampir pasti merupakan pelaksanaan yang buruk. Kita harus menambah lajur pada jadual penyair untuk menyimpan zaman kematian. Kod Java tidak peduli bagaimana skema pangkalan data dilaksanakan, kerana ia hanya memanggil prosedur tersimpan. Kami boleh menukar skema pangkalan data kemudian untuk meningkatkan prestasi, tetapi kami tidak perlu mengubah suai kod kami.
Berikut ialah kod Java yang memanggil prosedur tersimpan di atas:
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(); } }
Untuk memastikan kebolehselenggaraan, disyorkan untuk menggunakan kaedah statik seperti di sini. Ini juga menumpukan kod yang memanggil prosedur tersimpan ke dalam kod templat mudah. Jika anda menggunakan banyak prosedur tersimpan, anda akan mendapati bahawa anda boleh mencipta kaedah baharu dengan hanya menyalin dan menampal. Kerana templat kod, ia juga mungkin untuk menjana kod secara automatik yang memanggil prosedur tersimpan melalui skrip.
Fungsi
Prosedur tersimpan boleh mempunyai nilai pulangan, jadi kelas CallableStatement mempunyai kaedah seperti getResultSet untuk mendapatkan nilai pulangan. Apabila prosedur tersimpan mengembalikan nilai, anda mesti menggunakan kaedah registerOutParameter untuk memberitahu pemacu JDBC apakah jenis SQL nilai itu. Anda juga mesti melaraskan panggilan prosedur tersimpan untuk mengarahkan prosedur mengembalikan nilai.
Contoh di atas bersambung di bawah. Kali ini kita melihat umur Dylan Thomas pada masa kematiannya. Kali ini prosedur tersimpan menggunakan pl/pgsql PostgreSQL:
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;' language 'plpgsql';
下面是调用该存储过程的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结合使用,是一个非常强大的工具。
Atas ialah kandungan terperinci Bagaimana untuk memanggil prosedur tersimpan di Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!