开发篇:国产数据库达梦通过外部C/Java外部函数实现自定义函数的方法(详细过程)
开发篇:国产数据库达梦通过外部C/Java外部函数实现自定义函数的方法(详细过程)
最近在项目上遇到一个很有意思的问题,关于如何通过 C 或 Java 将外部函数注册到达梦中。在大数据开发领域,自定义函数是一个很常见的开发内容,在使用数据库内置函数无法实现业务需要的数据处理逻辑时,往往需要通过 UDF(User-defined function)来实现,尤其常见于上下游数据对接时,出于数据安全考虑需要进行加解密的场景。
对于大数据场景下的 UDF 函数而言,使用频率较高的应该是在 Hive 上,而由于 Hadoop 生态基于 Java 开发,基于 Java 进行 Hive 基础 UDF 开发会比较方便 —— 核心是继承 UDF 类并实现 evaluate 方法,在 evaluate 中编写数据处理逻辑并返回结果到 Hive。
而对于达梦数据库来说,其 Java 外部函数的运行机制和 Hive 差异较大:达梦无法像 Hive 那样在数据库进程内直接执行 Java 函数,需要依赖 dmagent 代理服务加载指定的 jar 包、运行 Java 函数逻辑,并在执行后将结果返回给数据库服务器。因此,在使用 Java 外部函数前,需要保证 dmagent 服务处于正常启动状态。对达梦数据库来说,如果不是业务强依赖 Java 生态,使用 C 开发外部函数会更合适(C 外部函数无需依赖 dmagent,执行性能也更优)。
随着国产化进程的推进,国产数据库也逐渐走入应用市场,但目前在相关技术论坛和社区上关于达梦、海量相关的技术分享还是比较少,很多场景(坑)在论坛里也找不到先例。本文中我整合了一些论坛上的相关资料,针对 Java 实现达梦外部函数这部分内容,补充了一些自己实践以后发现需要注意的要点。本文会体现出每一个步骤的详细流程,并尝试讲清楚每一个步骤的意义,让读者知其所以然。
言归正传,下面开始详细讲述如何使用Java进行达梦外部函数的开发与注册(以加解密函数为例),C版的后续有时间会同步更新到博客上。以下的示例是在windows上进行的,Linux的操作流程其实是一模一样的,区别只在于终端上的指令不同,博主最近项目上很忙,后续大家有需求并且我有时间的话可以出一个Linux纯享版。
操作系统:Windows 10
开发工具:IntelliJ IDEA
数据库:达梦
Web服务器:Tomcat
一:达梦数据库前期准备
首先需要保证操作的用户具有DBA权限,如果没有需要手动赋权,才能创建外部函数。
1.打开达梦数据库允许创建外部函数的开关。
需要修改配置文件,达梦默认是拒绝创建外部函数的。在会话中或者disql模式下执行指令都可以,指令如下:
SF_SET_SYSTEM_PARA_VALUE('ENABLE_EXTERNAL_CALL',1,0,2);
执行指令后需要重启数据库服务
2.保证达梦数据库配置的JAVA外部函数运行端口和dmagent代理服务的端口是一致的。如果之前没有修改过,默认应该是一致的。
dm\data\DAMENG\dm.ini
EXTERNAL_JFUN_PORT = 6363
dm\tool\dmagent\agent.ini
ap_port = 6363
3.安装dmagent服务
到dmagent目录下,执行安装指令(此处演示用的操作系统是windows,如果是Linux的话,执行service.sh指令)
service.bat install
4.部署Dem服务。
Dem需要自己部署,达梦提供了封装号的war包,但是需要我们手动部署好并进行一些调整。DEM是达梦的企业管理系统,里面提供了代理管理、系统运维、角色管理等等功能。部署该服务后,可以通过Dem观察dmagent的状态,并且dmagent服务启动后会不断地连接Dem,如果Dem未启动的话agent会一直报错。
部署步骤:
将dem的war包复制到tomcat的webapps下(tomcat服务启动的时候会自动扫描webapps下所有的war包并解析)
\dm\web\dem.war
修改tomcat server.xml的配置,增加参数maxPostSize="-1"
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxParameterCount="1000"
maxPostSize="-1"
/>
执行dem_init.sql脚本
set CHAR_CODE UTF8;
start dm\web\dem_init.sql
启动tomcat服务
tomcat\bin\startup.bat
解析完dem包后修改db.xml配置文件。非集群部署的话,IP建议用本机地址
<?xml version="1.0" encoding="UTF-8"?>
<ConnectPool>
<Server>127.0.0.1</Server>
<Port>5236</Port>
<User>SYSDBA</User>
<Password>123456789</Password>
<InitPoolSize>50</InitPoolSize>
<CorePoolSize>100</CorePoolSize>
<MaxPoolSize>500</MaxPoolSize>
<KeepAliveTime>60</KeepAliveTime>
<DbDriver></DbDriver>
<DbTestStatement>select 1</DbTestStatement>
<SSLDir>../sslDir/client_ssl/SYSDBA</SSLDir>
<SSLPassword></SSLPassword>
</ConnectPool>
重启 tomcat 服务
tomcat\bin\shutdown.bat
tomcat\bin\startup.bat
- 部署 dmagent 服务
修改agent的配置参数
agent.ini配置文件路径:
dm/tool/dmagent/agent.ini
开启与Dem服务的连接并配置对于的IP、端口和路由地址
center.url = http://127.0.0.1:8080/dem
gather_enable=true
service_enable=true
启动 dmagent 服务
start.bat agent.ini
二: Java 自定义函数开发
对类和方法没有特别的规定,正常按业务逻辑进行开发即可。需要注意的是出口函数的返回类型,需要和数据库函数的类型保持一致。
下面是一个使用RSA进行非对称加密的简单示例:
package cn.hychang;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* @Title: DemoRSA
* @Author 黄永昌
* @Package cn.hychang
* @Date 2023/10/31 21:38
* @description: 使用RSA非对称加解密的示例
*/
public class DemoRSA {
public static void main(String[] args) throws Exception {
String plainText = "Hello, World!";
// 生成公钥和私钥
KeyPair keyPair = generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 转换为字符串形式
String publicKeyString = keyToString(publicKey);
String privateKeyString = keyToString(privateKey);
// 打印公钥和私钥
System.out.println("Public Key: " + publicKeyString);
System.out.println("Private Key: " + privateKeyString);
// 加密
String encryptedText = encrypt(plainText, publicKeyString);
System.out.println("Encrypted Text: " + encryptedText);
// 解密
String decryptedText = decrypt(encryptedText, privateKeyString);
System.out.println("Decrypted Text: " + decryptedText);
}
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
public static String keyToString(Key key) {
byte[] keyBytes = key.getEncoded();
return Base64.getEncoder().encodeToString(keyBytes);
}
public static String encrypt(String plainText, String publicKeyString) throws Exception {
PublicKey publicKey = stringToPublicKey(publicKeyString);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public static String decrypt(String encryptedText, String privateKeyString) throws Exception {
PrivateKey privateKey = stringToPrivateKey(privateKeyString);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static PublicKey stringToPublicKey(String publicKeyString) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyString);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
return keyFactory.generatePublic(keySpec);
}
public static PrivateKey stringToPrivateKey(String privateKeyString) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyString);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
return keyFactory.generatePrivate(keySpec);
}
}
将代码封装成jar包,这时候要注意加解密方法的参数列表和返回值类型,需要和设计好的数据库函数所需参数和返回值相匹配。
将jar包放入dm根目录下,新建一个jar文件夹用来管理Java外部函数。
在达梦数据库中创建解密函数:
CREATE OR REPLACE FUNCTION decrypt_rsa(encryptedText varchar,privateKeyString varchar)
RETURN varchar
EXTERNAL 'D:\Developer\DataBase\dmdbms\bin\external_jar\DemoRSA.jar' "cn.hychang.DemoRSA.decrypt" USING java;
创建成功后通过decrypt_rsa函数进行解密,参数为密文 encryptedText 和私钥 privateKeyString ,函数执行成功后会返回解密后的明文。
select decrypt_rsa('JRzYChfYTJ9t6x0y6uxAbaEVPJTpKGot+CQ==','MIIKekCk0sUG35fY9Q/sY84xuOZlZhfJiiDcQdba5qnZ9quncvgsisG03a7icUF1AEzQmOwQ==')
由于时间和精力的原因,暂时记录到此,有些没有交待清楚的地方后续有空会再补充下。以及时间实在是忙不过来,贴图也比较少,只在最后贴了一个函数的执行截图,原因是博主目前没有在自己的服务器上做图片存储,这是出于网页加载体验的考虑,博主的服务器资源有限,图片太多会导致网页加载较慢。目前博客中所有的图片都是引用其他的图床实现的,目前也没有精力去上传太多的图片,后续有时间可以补一补。
如果觉得这篇文章对您有帮助,欢迎点击文章底部的赞赏,扫描二维码表达对博主的支持,谢谢大家!
有任何疑问和建议欢迎评论区留言,博主看到一定会回复,和大家一起探讨每一个技术问题,希望大家能一起进步!

