2023-10-31 21:17:13 星期二
最近在项目上遇到一个很有意思的问题,关于如何通过C或Java将外部函数注册到达梦中。在大数据开发领域,自定义函数是一个很常见的开发内容,在使用数据库内置函数无法实现业务需要的数据处理逻辑时,往往需要通过UDF(Userdefined function)来实现,尤其常见于上下游数据对接时,出于数据安全考虑需要进行加解密的场景。
对于大数据场景下的UDF函数而言,使用频率较高的应该是在Hive上,而由于Hadoop体系对Java天然的亲和性,基于Java进行Hive上的UDF开发会比较方便,需要注意的一个要点就是需要实现evaluate方法,在evaluate中返回处理结果到Hive。
而对于达梦数据库来说,对Java的支持并不像Hive那样友好,达梦本身是无法解析jar包的,需要通过代理服务dmagent代理进程装载指定的 jar 包,并在函数执行后将结果返回给服务器。因此,在使用Java外部函数前,需要保证dmagent服务处在正常启动状态。对达梦数据库来说,如果不是业务需要,个人觉得使用C开发外部函数会更合适。
随着国产化进程的推进,国产数据库也逐渐走入应用市场,但目前在相关技术论坛和社区上关于达梦、海量相关的技术分享还是比较少,很多场景(坑)在论坛里也找不到先例。本文中我整合了一些论坛上的相关资料,针对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
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
对类和方法没有特别的规定,正常按业务逻辑进行开发即可。需要注意的是出口函数的返回类型,需要和数据库函数的类型保持一致。
下面是一个使用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==')
由于时间和精力的原因,暂时记录到此,有些没有交待清楚的地方后续有空会再补充下。以及时间实在是忙不过来,贴图也比较少,只在最后贴了一个函数的执行截图,原因是博主目前没有在自己的服务器上做图片存储,这是出于网页加载体验的考虑,博主的服务器资源有限,图片太多会导致网页加载较慢。目前博客中所有的图片都是引用其他的图床实现的,目前也没有精力去上传太多的图片,后续有时间可以补一补。
如果觉得这篇文章对您有帮助,欢迎点击文章底部的赞赏,扫描二维码表达对博主的支持,谢谢大家!
有任何疑问和建议欢迎评论区留言,博主看到一定会回复,和大家一起探讨每一个技术问题,希望大家能一起进步!
评论