原创

开发篇:国产数据库达梦通过外部C/Java外部函数实现自定义函数的方法(详细过程)

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
  1. 部署 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==')

调用结果如图所示:
piurYR0.md.png

    由于时间和精力的原因,暂时记录到此,有些没有交待清楚的地方后续有空会再补充下。以及时间实在是忙不过来,贴图也比较少,只在最后贴了一个函数的执行截图,原因是博主目前没有在自己的服务器上做图片存储,这是出于网页加载体验的考虑,博主的服务器资源有限,图片太多会导致网页加载较慢。目前博客中所有的图片都是引用其他的图床实现的,目前也没有精力去上传太多的图片,后续有时间可以补一补。
    如果觉得这篇文章对您有帮助,欢迎点击文章底部的赞赏,扫描二维码表达对博主的支持,谢谢大家!
    有任何疑问和建议欢迎评论区留言,博主看到一定会回复,和大家一起探讨每一个技术问题,希望大家能一起进步!

Java
Linux
大数据
Hadoop
  • 作者:年轻的空指针(联系作者)
  • 发表时间:2023-11-01 22:17
  • 版权声明:严禁商用,转载请注明出处
  • 公众号转载:请在文末添加作者公众号二维码
  • 评论