首页 > 数据库 > > 用户自定义函数-PL/Java语言函数
使用GaussDB 200数据库的PL/Java函数,用户可以使用自己喜欢的Java IDE编写Java方法,并将包含这些方法的jar文件安装到GaussDB 200数据库中,然后使用该方法。GaussDB 200 PL/Java基于开源Greenplum PL/Java 1.4.0开发,PL/Java所使用的JDK版本为1.8.0_201。
Java UDF可以实现一些java逻辑计算,强烈建议不要在Java UDF中封装业务
使用PL/Java函数时,需要首先将Java方法的实现打包为jar包并且部署到数据库中,然后使用数据库管理员账号创建函数,考虑兼容性问题,请使用1.8.0_201版本的JDK进行编译。
Java方法的实现和出包可以借助IDE来实现,以下是一个通过命令行来进行编译和出包的简单的示例,通过这个简单示例可以创建出一个包含单个方法的jar包文件。
首先,编写一个Example.java文件,在此文件中实现子字符串大写转换的方法,内容如下:
public class Example
{
public static String upperString (String text, int beginIndex, int endIndex)
{
return text.substring(beginIndex, endIndex).toUpperCase();
}
}然后,创建manifest.txt清单文件,文件内容如下:
Manifest-Version: 1.0 Main-Class: Example Specification-Title: "Example" Specification-Version: "1.0" Created-By: 1.6.0_35-b10-428-11M3811 Build-Date: 08/14/2018 10:09 AM
最后,编译java文件并打包得到javaudf-example.jar
javac Example.java jar cfm javaudf-example.jar manifest.txt Example.class

jar包的命名规则应符合JDK命名要求,如果含有非法字符,在部署或者使用函数时将出错。
Jar包的部署需要omm用户在命令行中调用gs_om工具完成。以操作系统用户omm登录CN所在主机,执行source ${BIGDATA_HOME}/mppdb/.mppdbgs_profile命令启动环境变量。
首先,修改jar包权限:
chmod 644 javaudf-example.jar
然后,将jar包部署到数据库中:
gs_om –t JavaUDF –m addjar –s javaudf-example.jar
gs_extend_library函数如何使用请参见 管理jar包和文件。函数中的AK/SK值,请用户根据实际获取值替换。region_name请用户根据实际所在的区域名称替换。
首先,使用拥有sysadmin权限的数据库用户(例如:omm)登录数据库并创建java_upperstring函数如下:
CREATE FUNCTION java_upperstring(VARCHAR, INTEGER, INTEGER)
RETURNS VARCHAR
AS 'Example.upperString'
LANGUAGE JAVA;
说明:然后,执行java_upperstring函数:
SELECT java_upperstring('test', 0, 1);得到预期结果为:
java_upperstring --------------------- T (1 row)
创建普通用户,名称为udf_user,密码为'GAUSS@123'。
CREATE USER udf_user PASSWORD 'GAUSS@123';
授权普通用户udf_user对java_upperstring函数的使用权限。注意,此处需要把函数所在模式和函数的使用权限同时赋予给用户,用户才可以使用此函数。
GRANT ALL PRIVILEGES ON SCHEMA public TO udf_user; GRANT ALL PRIVILEGES ON FUNCTION java_upperstring(VARCHAR, INTEGER, INTEGER) TO udf_user;
以普通用户udf_user登录数据库。
SET SESSION SESSION AUTHORIZATION udf_user PASSWORD 'GAUSS@123';
执行java_upperstring函数:
SELECT public.java_upperstring('test', 0, 1);得到预期结果为:
java_upperstring --------------------- T (1 row)
DROP FUNCTION java_upperstring;
Jar包的卸载同样需要omm用户通过命令行调用gs_om工具完成:
gs_om –t JavaUDF –m rmjar –d javaudf-example.jar
目前可以通过两种方式进行部署,一种是使用gs_om命令行工具,该工具可以部署任意格式文件,另一种是使用SQL函数gs_extend_library进行部署,该方式功能比较有限,建议线下用户使用gs_om命令行工具完成jar包或文件的部署。
用户通过gs_om –t javaUDF –m addjar [-s LocalPath] [-d RemoteRelativePath] 命令实现jar包和其他配置文件的安装部署。
说明:-t javaUDF:操作名称。
-m addjar:表明操作类型为新增文件。
-s LocalPath:命令执行节点的绝对路径,可以是文件也可以是路径,不能为空。
-d RemoteRelativePath:相对路径,可缺省,若LocalPath为目录,该操作可以把LocalPath目录下的所有文件以及子目录及文件拷贝到集群中所有服务器的$GUASSHOME/lib/java/[RemoteRelativePath]目录下;若LocalPath为文件,该操作可以把LocalPath-文件拷贝到集群中所有服务器的$GUASSHOME/lib/java/[RemoteRelativePath]目录下。拷贝过程中如果存在冲突,提供是否覆盖选项y/n/a,分别表示是、否、全部替换,由用户根据实际需要自行选择策略。
用户通过gs_om –t javaUDF –m rmjar [-d RemoteRelativePath] 命令实现jar和其他配置文件的卸载。
说明:-t javaUDF:操作名称。
-m rmjar:表明操作类型为删除文件。
-d RemoteRelativePath:相对路径,可缺省,该操作可以删除集群中所有节点的指定目录$GUASSHOME/lib/java/[RemoteRelativePath] 及其下层目录下的目录、jar包和文件。
线下用户通过gs_om -t javaUDF –m ls [-d RemoteRelativePath]命令查看已部署的jar和配置文件。并对文件一致性做校验。
说明:-t javaUDF:操作名称。
-m ls:表明操作类型为查看文件。
-d RemoteRelativePath:相对路径,可缺省,该操作可以查看集群中所有节点的指定目录$GUASSHOME/lib /java/[RemoteRelativePath] 及其下层目录下的目录、jar包和文件(包括每个jar包或配置文件的相对路径),以执行节点为标准,对各节点中jar包及配置文件进行一致性检查。一致性检查会检查名称和MD5码,若有不一致给以警告。
PL/Java函数通过CREATE FUNCTION语法创建,并且定义为LANGUAGE JAVA,且包含RETURNS和AS子句。
CREATE [ OR REPLACE ] FUNCTION function_name
( [ { argname [ argmode ] argtype [ { DEFAULT | := | = } expression ]} [, …] ])
[ RETURNS rettype [ DETERMINISTIC ] ]
LANGAUGE JAVA
[
{ IMMUTABLE | STATBLE | VOLATILE }
| [ NOT ] LEAKPROOF
| WINDOW
| { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT |STRICT }
| {[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER | AUTHID DEFINER | AUTHID CURRENT_USER}
| { FENCED }
| COST execution_cost
| ROWS result_rows
| SET configuration_parameter { {TO |=} value | FROM CURRENT}
] […]
{
AS 'class_name.method_name' ( { argtype } [, …] )
}执行时,PL/Java会根据jar包名的字母序列,在所有部署的jar包中寻找函数指定的Java类,并调用首次找到的类中函数所指定的Java方法,并返回调用结果。
PL/Java函数通过DROP FUNCTION语法删除函数。更多语法说明,请参见DROP FUNCTION。
DROP FUNCTION [ IF EXISTS ] function_name [ ( [ {[ argmode ] [ argname ] argtype} [, ...] ] ) [ CASCADE | RESTRICT ] ];需要注意的是,如果所删除的函数为重载函数(详见重载函数),则删除时需要指明该函数的参数类型,如为非重载函数,则可直接指定函数名进行删除。
非sysadmin户无法创建PL/Java函数,sysadmin用户可以赋予其他类型用户使用函数的权限。更多语法说明,请参见GRANT。
GRANT { EXECUTE | ALL [ PRIVILEGES ] }
ON { FUNCTION {function_name ( [ {[ argmode ] [ arg_name ] arg_type} [, ...] ] )} [, ...]
| ALL FUNCTIONS IN SCHEMA schema_name [, ...] }
TO { [ GROUP ] role_name | PUBLIC } [, ...]
[ WITH GRANT OPTION ];GaussDB 200 | Java |
|---|---|
BOOLEAN | boolean |
"char" | byte |
bytea | byte[] |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
FLOAT4 | float |
FLOAT8 | double |
CHAR | java.lang.String |
VARCHAR | java.lang.String |
TEXT | java.lang.String |
name | java.lang.String |
DATE | java.sql.Timestamp |
TIME | java.sql.Time (stored value treated as local time) |
TIMETZ | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
TIMESTAMPTZ | java.sql.Timestamp |
GaussDB 200支持基础数组类型的转换,只需要在创建函数时在数据类型后追加 [] 即可,例如:
CREATE FUNCTION java_arrayLength(INTEGER[])
RETURNS INTEGER
AS 'Example.getArrayLength'
LANGUAGE JAVA;Java代码类似于:
public class Example
{
public static int getArrayLength(Integer[] intArray)
{
return intArray.length;
}
}那么下面的调用的语句后:
SELECT java_arrayLength(ARRAY[1, 2, 3]);
得到预期结果应该如下所示:
java_arrayLength --------------------- 3 (1 row)
对于默认与Java的简单类型进行映射转换的那些GaussDB 200数据类型,是无法处理NULL值的,如果希望在Java方法里能够获得并处理从GaussDB 200中传入的NULL值,可以使用Java的封装类,并通过以下方式在AS子句中指定该Java封装类:
CREATE FUNCTION java_countnulls(INTEGER[])
RETURNS INTEGER
AS 'Example.countNulls(java.lang.Integer[])'
LANGUAGE JAVA;Java代码类似于:
public class Example
{
public static int countNulls(Integer[] intArray)
{
int nullCount = 0;
for (int idx = 0; idx < intArray.length; ++idx)
{
if (intArray[idx] == null)
nullCount++;
}
return nullCount;
}
}那么下面的调用的语句后:
SELECT java_countNulls(ARRAY[null, 1, null, 2, null]);
得到的预期结果应该如下所示:
java_countNulls -------------------- 3 (1 row)
PL/Java支持重载函数,因此可以创建同名函数,或者调用Java代码中的重载方法。步骤如下:
例如,在Java中可以实现两个方法名相同,输入参数类型不同的方法dummy(int) 和dummy(String)
public class Example
{
public static int dummy(int value)
{
return value*2;
}
public static String dummy(String value)
{
return value;
}
}并在GaussDB 200中创建两个同名函数分别指定为上述两个方法:
CREATE FUNCTION java_dummy(INTEGER)
RETURNS INTEGER
AS 'Example.dummy'
LANGUAGE JAVA;
CREATE FUNCTION java_dummy(VARCHAR)
RETURNS VARCHAR
AS 'Example.dummy'
LANGUAGE JAVA;在调用重载函数时,GaussDB 200会根据输入的参数类型去调用匹配该类型的Java方法。因此上述两个函数的调用结果如下所示:
SELECT java_dummy(5);
java_dummy
-----------------
10
(1 row)
SELECT java_dummy('5');
java_dummy
---------------
5
(1 row)需要注意的是,由于GaussDB 200对数据类型存在隐式转换的情况,因此建议在调用重载函数时,指定输入参数的类型,例如:
SELECT java_dummy(5::varchar); java_dummy ---------------- 5 (1 row)
此时会优先匹配所指定的参数类型,如果不存在指定参数类型的Java方法,则会对参数进行隐式转换匹配转换后的参数类型对应的Java方法。
SELECT java_dummy(5::INTEGER); java_dummy ----------------- 10 (1 row) DROP FUNCTION java_dummy(INTEGER); SELECT java_dummy(5::INTEGER); java_dummy ---------------- 5 (1 row)

隐式转换的数据类型包括:
对于重载函数,删除时需要指定函数的参数类型,否则无法删除。
DROP FUNCTION java_dummy(INTEGER);
会话级别的GUC参数,该参数用于设置JVM的启动参数,例如:
SET pljava_vmoptions='-Xmx64m –Xms2m –XX:MaxMetaspaceSize=8m';
pljava_vmoptions可接受的参数包括:

[mount_path] read /path/to/java.ext.dirs

如果所设置的参数不在上述范围内,视为非法参数,会在调用函数时报错。
SET pljava_vmoptions=' illegal.option'; SET SELECT java_dummy(5::int); ERROR: UDF Error:cannot use PL/Java before successfully completing its setup.Please check if your pljava_vmoption is set correctly,since we do not ignore illegal parameters.Or check the log for more messages.
系统级别的GUC参数,用于限制每个CN、DN执行UDF可以使用的物理内存量,默认为200MB。可通过修改postgresql.conf文件进行配置,配置后需要重启数据库服务后才可生效。

例如:在某DN上把max_process_memory设置为10GB,udf_memory_limit设置为4GB,则此DN最多使用10GB - (4GB - 200MB)=6.2GB内存。即使用户没有执行任何UDF,则此DN也最多只能使用6.2GB内存。默认情况下,udf_memory_limit为200MB。查询pv_total_memory_detail视图时可以发现,process_used_memory永远不会超过max_process_memory - (udf_memory_limit - 200MB)。
系统级别的GUC参数,用于限制系统允许的单个Fenced UDF Worker进程的最大虚拟内存使用量,默认为1GB。可以通过修改postgresql.conf文件进行配置,配置后需要重启数据库服务后才可生效。

会话级别的GUC参数,用户限制会话发起的单个Fenced UDF Worker进程的最大虚拟内存使用量,设置方法如下:
SET FencedUDFMemoryLimit='512MB';
该参数的取值范围为 (150MB, UDFWorkerMemHardLimit],当设置大于UDFWorkerMemHardLimit时会立即报错,当设置小于等于150MB时,则会在调用函数时报错。

将配置文件config.properties部署到conf目录下:
chmod 644 config.properties gs_om –t javaUDF –m addjar –s config.properties –d conf/
可通过系统属性javaudf.file.path获得配置文件在GaussDB 200中的根路径:
public class Example
{
public static String readConfigFile()
{
String path = System.getProperty("javaudf.file.path") + "/conf/config.properties";
File file = new File(path);
BufferedReader reader = new BufferedReader(new FileReader(file));
String content = reader.readLine();
reader.close();
return content;
}
}在使用HDFS时,需要指定Hadoop的位置,同时使用kerberos方式获取访问Hadoop文件系统的权限。
如果物理节点上配置有HADOOP_HOME环境变量,PL/Java则会自动加载HADOOP_HOME/share/Hadoop/common,HADOOP_HOME/share/Hadoop/common/lib,HADOOP_HOME/share/Hadoop/hdfs,HADOOP_HOME/share/Hadoop/hdfs/lib,HADOOP_HOME/share/Hadoop/mapreduce,HADOOP_HOME/share/Hadoop/mapreduce/lib中的jar包。
用户也可以通过JVM启动参数-Dhadoop.home.dir=$HADOOP_HOME来指明Hadoop的位置。如果设置该启动参数,PL/Java则会以此参数为准加载该参数下上述路径中的jar包。
用户也可以通过设置-Djava.ext.dirs来加载所需要的Hadoop的依赖jar包,不过需要注意是,设置该参数时,需要同时追加JDK的依赖jar包路径,否则可能导致部分JDK功能缺失。
SET pljava_vmoptions='-Dhadoop.home.dir=$HADOOP_HOME'; SET pljava_vmoptions= '-Djava.ext.dirs=$GAUSSHOME/jdk/jre/lib/ext:HADOOP_HOME_PATH/share/Hadoop/common: HADOOP_HOME_PATH/share/Hadoop/common/lib';
同时,用户需要将HDFS相关的配置文件通过gs_om工具部署到conf目录下(详见部署使用配置文件),那么,用户在Java方法中则可以使用如下方式访问Hadoop中的文件。
public static String readHDFSFile(String filePath) throws Exception
{
FSDataInputStream in = null;
FileSystem fs = null;
String line = null;
try
{
String confpath = System.getProperty("javaudf.file.path") + "/conf/";
Configuration conf = new Configuration();
conf.addResource(new Path(confpath+"core-site.xml"));
conf.addResource(new Path(confpath+"hdfs-site.xml"));
System.setProperty("java.security.krb5.conf", confpath+"krb5.conf");
org.apache.hadoop.security.UserGroupInformation.setConfiguration(conf);
UserGroupInformation.loginUserFromKeytab("user@HADOOP.COM", confpath+"user.keytab");
fs = FileSystem.get(conf);
in = fs.open(new Path(filePath));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
line = bufferedReader.readLine();
in.close();
fs.close();
return line;
}
catch (Exception e)
{
return e.getMessage();
}
}对于只需要通过Java classpath加载的第三方库,可以简单的通过jar包部署到数据库中即可使用,而对于需要通过Java extension加载的第三方库(例如bcprov-ext-jdk15on-153.jar),可以尝试如下两种方式进行加载:
SET pljava_vmoptions='-Djava.ext.dirs=$GAUSSHOME/jdk/jre/lib/ext:$PATH_TO_THIRD_LIB';
其中,$GAUSSHOME需要替换成绝对路径;$PATH_TO_THIRD_LIB需要替换为第三方库所在的绝对路径。可能需要在沙箱的配置文件中指定$PATH_TO_THIRD_LIB对UDF Worker可见,详细见pljava_vmoptions中的注意,如果第三方库所在目录的某层上级目录已经对UDF Worker可见,则不需要指定
如果在JVM中发生异常,PL/Java的异常处理机制会将异常时JVM的堆栈信息输出到客户端和pg_log文件中。为了便于用户调试,用户可以通过pg_log文件来查看异常发生前后数据库的运行情况,同时也可以通过hs_err_pidxxx.log文件查看JVM runtime的异常信息。
PL/Java使用标准的Java Logger。 因此,用户可以通过如下方式记录日志:
Logger.getAnonymousLogger().config( "Time is " + new Date(System.currentTimeMillis()));
初始化的Java Logger类会默认设置为CONIFG级别,对应为GaussDB 200的LOG级别。Java Logger类输出的日志消息都会重定向到GaussDB 200后端,并写入到服务器日志或显示在用户界面上。MPPDB服务器日志将记录LOG、WARNING、ERROR级别的信息,而SQL用户界面将显示WARNING和ERROR级别的日志消息。Java Logger级别与GaussDB 200的日志级别对应关系见下表。
java.util.logging.Level | GaussDB 200 日志级别 |
|---|---|
SERVER | ERROR |
WARINING | WARNING |
CONFIG | LOG |
INFO | INFO |
FINE | DEBUG1 |
FINER | DEBUG2 |
FINEST | DEBUG3 |
用户可以通过以下方式更改Java Logger的记录级别。例如通过下面的Java代码修改Java Logger级别为SEVERE,此时再记录WARNING级别的日志时,日志消息(msg)就不会再写入到GaussDB 200日志中。
Logger log = Logger.getAnonymousLogger(); Log.setLevel(Level.SEVERE); log.log(Level.WARNING, msg);
在GaussDB 200中,PL/Java是一种untrusted语言,PL/Java函数只能由数据库sysdamin用户进行创建,通过GRANT方式赋予其他用户使用权限(详见函数授权)。
同时PL/Java控制用户对文件系统的访问权限,不允许用户在Java方法中对大部分的系统文件进行读操作,不允许所有的写、删除和执行操作。
允许的操作包括:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69959250/viewspace-3154240/ ,如需转载,请注明出处,否则将追究法律责任。