我们最近收到了一些关于 PL/Java 的问题,我发现在 Internet 上搜索很难获得明确的说明。并不是说那里没有好的信息,但大部分信息要么不完整、过时或令人困惑,我决定创建这个简短的“操作方法”并展示如何安装它以及如何通过几个示例运行它.
安装
我将在这里展示如何从源代码安装它,首先是因为我的平台没有编译好的二进制文件,其次是因为如果你的平台有来自包管理器的二进制文件,你可以从那里安装它,例如使用 YUM 或 APT . 另外,请注意,为了简单起见,我使用的 PL/Java 没有指定“受信任”和 Postgres 数据库超级用户。我建议在此处阅读有关用户和权限的文档[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
我在这里假设您已经足够了解 maven 并且不会使用“ mvn ”命令。“ java -jar pljava-packaging/target/pljava-pg12.jar ”会将所需的文件和包复制/安装到 Postgres 文件夹中。请注意,maven 使用了我的 Postgres 版本并创建了版本为“ pljava-pg12.jar ”的 jar 文件,因此请注意您的版本,因为如果您有不同的 Postgres 版本,jar 文件会发生变化!
我现在可以将扩展创建到我将使用它的数据库中。我在这个博客中使用了数据库“ 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".
不完全符合我的预期,但我得到了一个很好的提示:“提示:将 pljava.libjvm_location 设置为 jvm 库的正确路径(libjvm.so 或 jvm.dll 等) ”。好的,我必须找到我的系统用来配置 Postgres的libjvm。我使用 SET 命令在线完成:
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 ”使其在我的所有数据库中持久化,因为它将给定的参数设置写入“ postgresql.auto.conf ”文件,除了“ postgresql.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
现在我们已经安装了它,我们可以检查系统目录是否确实存在:
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 访问数据库对象
我发现的大多数示例都展示了如何从 Java 类中执行“hello world”或如何计算斐波那契数列,但没有展示如何访问数据库对象。嗯,这些例子没有错,但我想在他的数据库中安装 PL/Java 的人想要从 Java 函数内部访问数据库对象,这就是我们在这里要做的。
我将使用可在此处找到的示例数据库“ pagila ”用于本文中的测试。
对于第一个示例,我将创建一个带有静态方法的简单类,该类将像任何 Postgres 函数一样在外部访问。该函数将接收一个整数参数并使用它来搜索表“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);
}
}
我已经使用以下命令手动编译并创建了“ 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”中创建了 jar 文件,请注意,因为我们将在下一步中使用此信息,将 jar 文件加载到 Postgres 中:
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函数具有签名“ INSTALL_JAR(<jar_url>,<jar_name都>,<部署>) ”,并将其加载从由URL到SQLJ罐储存库指定的位置的jar文件。如果存储库中已存在具有给定名称的 jar,或者 URL 中不存在该 jar 或数据库无法读取它,则会出现错误:
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)
</strong>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>
函数set_classpath定义给定模式的类路径,在本例中为模式“public”。类路径由以冒号分隔的 jar 名称或类名称列表组成。如果给定的架构不存在,或者一个或多个 jar 名称引用了不存在的 jar,则这是一个错误。
下一步是创建 Postgres 函数:
demo=# CREATE FUNCTION getCustomerInfo( INT ) RETURNS CHAR AS
'com.percona.blog.pljava.Customers.getCustomerInfo( java.lang.Integer )'
LANGUAGE java;
CREATE FUNCTION
我们现在可以使用它:
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 演示数据库中有我们的第一个 Java 函数。
现在,在我们的最后一个示例中,我将向此类添加另一个方法,现在列出给定客户的所有付款并计算其总数:
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
然后我们需要替换新创建的加载的 jar 文件并在 Postgres 中创建函数:
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
结果是:
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)
我们在这里和最后一个例子完成这部分。在这一点上,我们能够访问对象,遍历结果集,并将结果作为单个对象(如 TEXT)返回。
[1]https://tada.github.io/pljava/use/policy.html
[2]https://www.postgresql.org/ftp/projects/pgFoundry/dbsamples/pagila/
Percona Distribution for PostgreSQL 在单个发行版中提供来自开源社区的最佳和最关键的企业组件,经过设计和测试可以协同工作。




