【譯】PostgreSQL PL/Java – 實(shí)操(一)

前言

此博客翻譯自 percona 官網(wǎng)博客 《PostgreSQL PL/Java – A How-To, Part 1

最近,我們收到了一些關(guān)于 PL/Java 的問題忽刽,我發(fā)現(xiàn)很難在互聯(lián)網(wǎng)上搜索到明確的說明。并不是說網(wǎng)上沒有優(yōu)質(zhì)的信息酌住,但其中大部分信息要么不完整吹艇,要么過時,要么令人困惑总寻,我決定創(chuàng)建這個簡短的“how-to”并展示如何安裝 PL/Java 谤草,以及如何通過幾個示例運(yùn)行 PL/Java衙傀。

安裝

我將在這里展示如何從源代碼安裝 PL/Java抬吟,因?yàn)槲业钠脚_沒有編譯的二進(jìn)制文件,如果你的平臺有來自包管理器的二進(jìn)制文件统抬,你可以從那里安裝火本,例如使用 YUM 或 APT危队。另外,請注意钙畔,為簡單起見茫陆,我使用的 PL/Java 沒有指定特定的可信用戶,而是使用了 Postgres 數(shù)據(jù)庫超級用戶擎析。我建議閱讀有關(guān)用戶和權(quán)限的文檔簿盅,在這[1]。

我使用的軟件版本如下:

  • PostgreSQL 12.7
  • PL/Java 1.6.2
  • OpenJDK 11
  • Apache Maven 3.6.3

我從“https://github.com/tada/pljava/releases”下載了源碼揍魂,然后解包并用 maven 編譯:

wget https://github.com/tada/pljava/archive/refs/tags/V1_6_2.tar.gz

tar -xf V1_6_2.tar.gz

cd pljava-1_6_2

mvn clean install

java -jar pljava-packaging/target/pljava-pg12.jar

我在這里假設(shè)你已經(jīng)足夠了解 maven 并且會使用 mvn 命令桨醋。 java -jar pljava-packaging/target/pljava-pg12.jar 會將所需的文件和包復(fù)制/安裝到 Postgres 文件夾中。 請注意现斋,maven 使用了我的 Postgres 版本并使用該版本創(chuàng)建了 jar 文件:“pljava-pg12.jar”喜最,因此請注意您已經(jīng)擁有的版本,因?yàn)槿绻胁煌?Postgres 版本庄蹋,jar 文件將會不一樣瞬内!

我現(xiàn)在可以將擴(kuò)展安裝到我將使用的數(shù)據(jù)庫中。我在這個博客里使用的數(shù)據(jù)庫叫 “demo”:

$ psql demo
psql (12.7)
Type "help" for help.

demo=# CREATE EXTENSION pljava;
WARNING: Java virtual machine not yet loaded
DETAIL: libjvm: cannot open shared object file: No such file or directory
HINT: SET pljava.libjvm_location TO the correct path to the jvm library (libjvm.so or jvm.dll, etc.)
ERROR: cannot use PL/Java before successfully completing its setup
HINT: Check the log for messages closely preceding this one, detailing what step of setup failed and what will be needed, probably setting one of the "pljava." configuration variables, to complete the setup. If there is not enough help in the log, try again with different settings for "log_min_messages" or "log_error_verbosity".

不完全是我所期待的限书,但我得到了一個很好的提示:“HINT: SET pljava.libjvm_location TO the correct path to the jvm library (libjvm.so or jvm.dll, etc.)“虫蝶。好的,我必須找到我的系統(tǒng)正在使用的 libjvm 來配置 Postgres蔗包。我使用 SET 命令在線執(zhí)行此操作:

demo=# SET pljava.libjvm_location TO '/usr/lib/jvm/java-11-openjdk-11.0.11.0.9-5.fc34.x86_64/lib/server/libjvm.so';
NOTICE: PL/Java loaded
DETAIL: versions:
PL/Java native code (1.6.2)
PL/Java common code (1.6.2)
Built for (PostgreSQL 12.7 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.1.1 20210531 (Red Hat 11.1.1-3), 64-bit)
Loaded in (PostgreSQL 12.7 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.1.1 20210531 (Red Hat 11.1.1-3), 64-bit)
OpenJDK Runtime Environment (11.0.11+9)
OpenJDK 64-Bit Server VM (11.0.11+9, mixed mode, sharing)
NOTICE: PL/Java successfully started after adjusting settings
HINT: The settings that worked should be saved (using ALTER DATABASE demo SET ... FROM CURRENT or in the "/v01/data/db/pg12/postgresql.conf" file). For a reminder of what has been set, try: SELECT name, setting FROM pg_settings WHERE name LIKE 'pljava.%' AND source = 'session'
NOTICE: PL/Java load successful after failed CREATE EXTENSION
DETAIL: PL/Java is now installed, but not as an extension.
HINT: To correct that, either COMMIT or ROLLBACK, make sure the working settings are saved, exit this session, and in a new session, either: 1. if committed, run "CREATE EXTENSION pljava FROM unpackaged", or 2. if rolled back, simply "CREATE EXTENSION pljava" again.
SET

還可以使用ALTER SYSTEM使其在我的所有數(shù)據(jù)庫中持久化秉扑,因?yàn)樗鼘⒔o定的參數(shù)設(shè)置寫入“postgresql.auto.conf”文件慧邮,Postgres 除了讀取 “postgresql.conf”之外還讀取 “postgresql.auto.conf” 文件:

demo=# ALTER SYSTEM SET pljava.libjvm_location TO '/usr/lib/jvm/java-11-openjdk-11.0.11.0.9-5.fc34.x86_64/lib/server/libjvm.so';
ALTER SYSTEM

現(xiàn)在我們已經(jīng)安裝了 PL/Java调限,我們可以檢查系統(tǒng) catalog 看它是否確實(shí)存在:

demo=# SELECT * FROM pg_language WHERE lanname LIKE 'java%';
oid    | lanname | lanowner | lanispl | lanpltrusted | lanplcallfoid | laninline | lanvalidator | lanacl
-------+---------+----------+---------+--------------+---------------+-----------+--------------+-------------------
16428  | java    | 10       | t       | t            | 16424         | 0         | 16427        | {charly=U/charly}
16429  | javau   | 10       | t       | f            | 16425         | 0         | 16426        |
(2 rows)

并測試它是否可用:

demo=# CREATE FUNCTION getProperty(VARCHAR)
RETURNS VARCHAR
AS 'java.lang.System.getProperty'
LANGUAGE java;
CREATE FUNCTION
demo=# SELECT getProperty('java.version');
getproperty
-------------
11.0.11
(1 row)

可以看到它是可用的!是時候嘗試一些有用的東西了误澳。

使用 PL/Java 訪問數(shù)據(jù)庫對象

我發(fā)現(xiàn)耻矮,大多數(shù)示例都是如何從 Java 類中創(chuàng)建“hello world”或如何計(jì)算斐波那契數(shù)列,但沒有展示如何訪問數(shù)據(jù)庫對象忆谓。好吧裆装,這些例子沒有錯,但我想認(rèn)為倡缠,那些在數(shù)據(jù)庫中安裝 PL/Java 的人想從 Java 函數(shù)內(nèi)部訪問數(shù)據(jù)庫對象哨免,這就是我們在這里要做的。

在這篇文章中昙沦,我將使用可在此處[2] 找到的示例數(shù)據(jù)庫 “pagila” 進(jìn)行測試琢唾。

對于第一個示例,我將創(chuàng)建一個帶有靜態(tài)方法的簡單類盾饮,該類將像任何 Postgres 函數(shù)一樣在外部訪問采桃。該函數(shù)將接收一個整數(shù)參數(shù)并使用它來搜索表“customer”懒熙、列“customer_id”,并將打印客戶的 ID普办、全名工扎、電子郵件和地址:

package com.percona.blog.pljava;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Customers {
    private static String m_url = "jdbc:default:connection";

    public static String getCustomerInfo(Integer id) throws SQLException {
        Connection conn = DriverManager.getConnection(m_url);
        String query = "SELECT c.customer_id, c.last_name ||', '|| c.first_name as full_name, "
                + " c.email, a.address, ci.city, a.district "
                + " FROM customer c"
                + "  JOIN address a on a.address_id = c.address_id "
                + " JOIN city ci on ci.city_id = a.city_id "
                + " WHERE customer_id = ?";

        PreparedStatement stmt = conn.prepareStatement(query);
        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();
        rs.next();
        String ret; 
        ret = "- ID: " + rs.getString("customer_id") ;
        ret += "\n- Name: " + rs.getString("full_name");
        ret += "\n- Email: " + rs.getString("email");
        ret += "\n- Address: " + rs.getString("address");
        ret += "\n- City: " + rs.getString("city");
        ret += "\n- District: " + rs.getString("district");
        ret += "\n--------------------------------------------------------------------------------";

        stmt.close();
        conn.close();

        return (ret);
    }
}

我使用以下命令手動編譯并創(chuàng)建了“jar”文件:

javac com/percona/blog/pljava/Customers.java
jar -c -f /app/pg12/lib/demo.jar com/percona/blog/pljava/Customers.class

請注意,我在“/app/pg12/lib”文件夾中創(chuàng)建了 jar 文件衔蹲,記住這一點(diǎn)肢娘,因?yàn)槲覀儗⒃谙乱徊街惺褂眠@些信息,在 Postgres 中加載 jar 文件:

demo=# SELECT sqlj.install_jar( 'file:///app/pg12/lib/demo.jar', 'demo', true );
 install_jar 
-------------
(1 row)

demo=# SELECT sqlj.set_classpath( 'public', 'demo' );
 set_classpath 
---------------
(1 row)

install_jar 函數(shù)具有簽名“install_jar(<jar_url>, <jar_name>, <deploy>)”踪危,該函數(shù)將一個 jar 文件從 URL 指定的位置加載到 SQLJ jar 存儲庫中蔬浙。如果給定名稱的 jar 已存在于存儲庫中,或者該 jar 在 URL 中不存在或數(shù)據(jù)庫無法讀取該 jar贞远,則會出現(xiàn)錯誤:

demo=# SELECT sqlj.install_jar( 'file:///app/pg12/lib/<strong>demo2.jar</strong>', 'demo', true );
<strong>ERROR:  java.sql.SQLException: I/O exception reading jar file: /app/pg12/lib/demo2.jar (No such file or directory)

demo=# SELECT sqlj.install_jar( 'file:///app/pg12/lib/demo.jar', 'demo', true );
 install_jar 
------------- 
(1 row)

demo=# SELECT sqlj.install_jar( 'file:///app/pg12/lib/demo.jar', 'demo', true );
<strong>ERROR:  java.sql.SQLNonTransientException: A jar named 'demo' already exists
</strong>

函數(shù) set_classpath 為給定模式定義類路徑畴博,在此示例中,模式為“public”蓝仲。類路徑由冒號分隔的 jar 名稱或類名稱列表組成俱病。如果給定的模式不存在或者一個或多個 jar 名稱引用了不存在的 jar,則這是一個錯誤袱结。

下一步是創(chuàng)建 Postgres 函數(shù):

demo=# CREATE FUNCTION getCustomerInfo( INT ) RETURNS CHAR AS 
    'com.percona.blog.pljava.Customers.getCustomerInfo( java.lang.Integer )'
LANGUAGE java;
CREATE FUNCTION

我們現(xiàn)在可以使用它:

demo=# SELECT getCustomerInfo(100);
                                 getcustomerinfo                                  
----------------------------------------------------------------------------------
 - ID: 100                                                                       +
 - Name: HAYES, ROBIN                                                            +
 - Email: ROBIN.HAYES@sakilacustomer.org                                         +
 - Address: 1913 Kamakura Place                                                  +
 - City: Jelets                                                                  +
 - District: Lipetsk                                                             +
 --------------------------------------------------------------------------------
(1 row)

親愛的亮隙,我們的 Postgres 演示數(shù)據(jù)庫中有我們的第一個 Java 函數(shù)。
現(xiàn)在垢夹,在我們的最后一個示例中溢吻,我將向此類添加另一個方法,現(xiàn)在列出來自給定客戶的所有付款并計(jì)算其總額:

package com.percona.blog.pljava;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Customers {
    private static String m_url = "jdbc:default:connection";

    public static String getCustomerInfo(Integer id) throws SQLException {
        Connection conn = DriverManager.getConnection(m_url);
        String query = "SELECT c.customer_id, c.last_name ||', '|| c.first_name as full_name, "
                + " c.email, a.address, ci.city, a.district "
                + " FROM customer c"
                + "  JOIN address a on a.address_id = c.address_id "
                + " JOIN city ci on ci.city_id = a.city_id "
                + " WHERE customer_id = ?";

        PreparedStatement stmt = conn.prepareStatement(query);
        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();
        rs.next();
        String ret; 
        ret = "- ID: " + rs.getString("customer_id") ;
        ret += "\n- Name: " + rs.getString("full_name");
        ret += "\n- Email: " + rs.getString("email");
        ret += "\n- Address: " + rs.getString("address");
        ret += "\n- City: " + rs.getString("city");
        ret += "\n- District: " + rs.getString("district");
        ret += "\n--------------------------------------------------------------------------------";

        stmt.close();
        conn.close();

        return (ret);
    }

    public static String getCustomerTotal(Integer id) throws SQLException {
        Connection conn;
        PreparedStatement stmt;
        ResultSet rs;
        String result;
        double total;

        conn = DriverManager.getConnection(m_url);
        stmt = conn.prepareStatement(
                "SELECT c.customer_id, c.first_name, c.last_name FROM customer c WHERE c.customer_id = ?");
        stmt.setInt(1, id);
        rs = stmt.executeQuery();
        if (rs.next()) {
            result = "Customer ID  : " + rs.getInt("customer_id");
            result += "\nCustomer Name: " + rs.getString("last_name") + ", " + rs.getString("first_name");
            result += "\n--------------------------------------------------------------------------------------------------------";
        } else {
            return null;
        }

        stmt = conn.prepareStatement("SELECT p.payment_date, p.amount FROM payment p WHERE p.customer_id = ? ORDER BY 1");
        stmt.setInt(1, id);
        rs = stmt.executeQuery();
        total = 0;

        while (rs.next()) {
            result += "\nPayment date: " + rs.getString("payment_date") + ",    Value: " + rs.getString("amount");
            total += rs.getFloat("amount");
        }
        result += "\n--------------------------------------------------------------------------------------------------------";
        result += "\nTotal: " +String.format("%1$,.2f",  total);
        
        stmt.close();
        conn.close();
        return (result);
    }
}

相同的編譯指令:

javac com/percona/blog/pljava/Customers.java 
jar -c -f /app/pg12/lib/demo.jar com/percona/blog/pljava/Customers.class

然后我們需要用新創(chuàng)建 jar文件替換之前加載的 jar 文件并在Postgres中創(chuàng)建函數(shù):

demo=# SELECT sqlj.replace_jar( 'file:///app/pg12/lib/demo.jar', 'demo', true );
 replace_jar 
-------------
(1 row)

demo=# CREATE FUNCTION getCustomerTotal( INT ) RETURNS CHAR AS 
    'com.percona.blog.pljava.Customers.getCustomerTotal( java.lang.Integer )'
LANGUAGE java;
CREATE FUNCTION

結(jié)果是:

demo=# SELECT getCustomerInfo(100);
                                 getcustomerinfo                                  
----------------------------------------------------------------------------------
 - ID: 100                                                                       +
 - Name: HAYES, ROBIN                                                            +
 - Email: ROBIN.HAYES@sakilacustomer.org                                         +
 - Address: 1913 Kamakura Place                                                  +
 - City: Jelets                                                                  +
 - District: Lipetsk                                                             +
 --------------------------------------------------------------------------------
(1 row)

我們在這里用最后一個例子結(jié)束這一節(jié)果元。 此時促王,我們可以訪問對象,遍歷結(jié)果集而晒,并將結(jié)果作為單個對象(如 TEXT)返回蝇狼。 我將在本文的第二部分和第三部分討論如何返回數(shù)組/結(jié)果集,如何在觸發(fā)器中使用 PL/Java 函數(shù)倡怎,以及如何使用外部資源迅耘,敬請期待!

引用:
[1] https://tada.github.io/pljava/use/policy.html
[2] https://www.postgresql.org/ftp/projects/pgFoundry/dbsamples/pagila/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末监署,一起剝皮案震驚了整個濱河市颤专,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钠乏,老刑警劉巖栖秕,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缓熟,居然都是意外死亡累魔,警方通過查閱死者的電腦和手機(jī)摔笤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垦写,“玉大人吕世,你說我怎么就攤上這事√萃叮” “怎么了命辖?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長分蓖。 經(jīng)常有香客問我尔艇,道長,這世上最難降的妖魔是什么么鹤? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任终娃,我火速辦了婚禮,結(jié)果婚禮上蒸甜,老公的妹妹穿的比我還像新娘棠耕。我一直安慰自己,他們只是感情好柠新,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布窍荧。 她就那樣靜靜地躺著,像睡著了一般恨憎。 火紅的嫁衣襯著肌膚如雪蕊退。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天憔恳,我揣著相機(jī)與錄音瓤荔,去河邊找鬼。 笑死喇嘱,一個胖子當(dāng)著我的面吹牛茉贡,可吹牛的內(nèi)容都是我干的塞栅。 我是一名探鬼主播者铜,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼放椰!你這毒婦竟也來了作烟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤砾医,失蹤者是張志新(化名)和其女友劉穎拿撩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體如蚜,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡压恒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年影暴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片探赫。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡型宙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伦吠,到底是詐尸還是另有隱情妆兑,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布毛仪,位于F島的核電站搁嗓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏箱靴。R本人自食惡果不足惜腺逛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衡怀。 院中可真熱鬧屉来,春花似錦、人聲如沸狈癞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝶桶。三九已至慨绳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間真竖,已是汗流浹背脐雪。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恢共,地道東北人战秋。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像讨韭,于是被迫代替她去往敵國和親脂信。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容