达梦JNI方式调用Logmnr接口调用示例
介绍
1) Logmnr 包是达梦数据库的日志分析工具,达梦提供了 JNI 接口和 C 接口,供应用程序直接调用。
2) 在使用 Logmnr 包中的接口之前,需要先开启两个日志相关参数:一是开启归档(设置 INI 参数 ARCH_INI 为 1);二是开启在日志中记录逻辑操作的功能(设置 INI 参数 RLOG_APPEND_LOGIC 为 1、2 或者 3)。
3) 本文重点JNI的方式解析数据库的归档日志,logmnr.jar包是达梦数据库的日志分析工具的JNI 接口,通过这个jar包解析归档日志。
4)在DM8程序员手册中,提供示例:https://eco.dameng.com/document/dm/zh-cn/pm/logmnr-interface-instructions.html#9.1.2%20%E7%BC%96%E7%A8%8B%E5%AE%9E%E4%BE%8B
编写用例代码
因手册中提供的示例比较简单,现对这块示例做一些补充,
1)实现解析指定目录下所有的归档文件同时将结果按不同文件保存,
2)说明linux跟window系统环境下运行方式。
3)示例中不再是解析100条数据写入文本,而是将这个归档所有的结果集写入到文本
4)为便于测试,将代码中的硬编码变量调整为传参形式
目录结构如下,包含 Java 源码、依赖库、日志归档文件等子目录和文件:
├── LogmnrTest.java # 主程序 Java 源文件
├── result/ # 程序运行的输出结果目录
├── lib/ # Java 依赖的 Jar 包目录
│ └── com.dameng.logmnr.jar # 达梦 LogMnr 接口 Jar 包
├── arch_file/ # 存放归档日志的目录(LogMnr 分析目标)
│ └── ARCHIVE_LOCAL_0x4B8158F0_EP0_2025-05-26_16-04-49.log
import com.dameng.logmnr.LogmnrDll;
import com.dameng.logmnr.LogmnrRecord;import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;/*** 日志分析工具* 使用方法: java LogmnrTest <数据库IP> <数据库端口> <数据库用户名> <数据库密码> [日志目录路径]* 示例: java LogmnrTest 192.168.112.168 5236 SYSDBA SYSDBA* 示例: java LogmnrTest 192.168.112.168 5236 SYSDBA SYSDBA D:\dmdbms\arch_file* 注意: 如果不指定日志目录路径,将使用程序同级目录下的arch_file目录*/
public class LogmnrTest
{private static void printUsage() {System.out.println("使用方法: java LogmnrTest <数据库IP> <数据库端口> <数据库用户名> <数据库密码> [日志目录路径]");System.out.println("示例: java LogmnrTest 192.168.112.168 5236 SYSDBA SYSDBA");System.out.println("示例: java LogmnrTest 192.168.112.168 5236 SYSDBA SYSDBA D:\\dmdbms\\arch_file");System.out.println("注意: 如果不指定日志目录路径,将使用程序同级目录下的arch_file目录");}private static String getDefaultLogDirectory() {// 获取程序运行目录String currentDir = System.getProperty("user.dir");return currentDir + File.separator + "arch_file";}private static List<File> getLogFiles(String directoryPath) {List<File> logFiles = new ArrayList<>();File directory = new File(directoryPath);if (!directory.exists() || !directory.isDirectory()) {System.err.println("错误: 目录不存在或不是有效目录: " + directoryPath);return logFiles;}File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.isDirectory()) {logFiles.addAll(getLogFiles(file.getAbsolutePath()));} else if (file.getName().toLowerCase().endsWith(".log")) {logFiles.add(file);}}}return logFiles;}private static void processLogFile(long connid, File logFile, String resultDir) throws Exception {LogmnrDll.addLogFile(connid, logFile.getAbsolutePath(), 3);LogmnrDll.startLogmnr(connid, -1, null, null);// 创建结果文件String resultFileName = logFile.getName().replace(".log", "_result.txt");File resultFile = new File(resultDir, resultFileName);PrintStream filePs = new PrintStream(resultFile);PrintStream consolePs = System.out; // 保存控制台输出流System.out.println("开始处理日志文件:" + logFile.getName());LogmnrRecord[] records;int batchSize = 100;int totalCount = 0;int recordIndex = 0;// 循环获取数据并立即写入文件while (true) {records = LogmnrDll.getData(connid, batchSize);if (records == null || records.length == 0) {break;}// 处理当前批次的数据for (LogmnrRecord record : records) {// 写入文件filePs.println("-----------------------------" + recordIndex + "-----------------------------" + "\n");filePs.println("xid:" + record.getXid() + "\n");filePs.println("operation:" + record.getOperation() + "\n");filePs.println("sqlRedo:" + record.getSqlRedo() + "\n");filePs.println("#############################" + recordIndex + "#############################" + "\n");filePs.println("scn:" + record.getScn() + "\n");filePs.println("startScn:" + record.getStartScn() + "\n");filePs.println("commitScn:" + record.getCommitScn() + "\n");filePs.println("timestamp:" + record.getTimestamp() + "\n");filePs.println("startTimestamp:" + record.getStartTimestamp() + "\n");filePs.println("commitTimestamp:" + record.getCommitTimestamp() + "\n");filePs.println("operationCode:" + record.getOperationCode() + "\n");filePs.println("rollBack:" + record.getRollBack() + "\n");filePs.println("segOwner:" + record.getSegOwner() + "\n");filePs.println("tableName:" + record.getTableName() + "\n");filePs.println("rowId:" + record.getRowId() + "\n");filePs.println("rbasqn:" + record.getRbasqn() + "\n");filePs.println("rbablk:" + record.getRbablk() + "\n");filePs.println("rbabyte:" + record.getRbabyte() + "\n");filePs.println("dataObj:" + record.getDataObj() + "\n");filePs.println("dataObjv:" + record.getDataObjv() + "\n");filePs.println("rsId:" + record.getRsId() + "\n");filePs.println("ssn:" + record.getSsn() + "\n");filePs.println("csf:" + record.getCsf() + "\n");filePs.println("status:" + record.getStatus() + "\n");filePs.println("#############################" + recordIndex + "#############################" + "\n");recordIndex++;}totalCount += records.length;// 进度信息同时输出到控制台和文件String progressMsg = "已处理 " + totalCount + " 条记录...";consolePs.println(progressMsg);filePs.println(progressMsg);filePs.flush(); // 确保数据及时写入文件}String completionMsg = "总共处理 " + totalCount + " 条记录";consolePs.println(completionMsg);filePs.println(completionMsg);consolePs.println("结果已保存到文件:" + resultFile.getAbsolutePath());filePs.flush();filePs.close();LogmnrDll.endLogmnr(connid, 1);}public static void main(String[] args) {// 检查参数数量if (args.length < 4 || args.length > 5) {System.err.println("错误: 参数数量不正确");printUsage();return;}try {// 解析参数String dbIp = args[0];int dbPort = Integer.parseInt(args[1]);String dbUser = args[2];String dbPassword = args[3];// 获取日志目录路径String logDirectory;if (args.length == 5) {logDirectory = args[4];} else {logDirectory = getDefaultLogDirectory();System.out.println("使用默认日志目录: " + logDirectory);}// 创建结果目录String resultDir = "result";File resultDirectory = new File(resultDir);if (!resultDirectory.exists()) {resultDirectory.mkdir();}LogmnrDll.initLogmnr();long connid = LogmnrDll.createConnect(dbIp, dbPort, dbUser, dbPassword);// 获取并处理日志文件List<File> logFiles = getLogFiles(logDirectory);if (logFiles.isEmpty()) {System.err.println("错误: 在指定目录下未找到日志文件: " + logDirectory);return;}System.out.println("找到 " + logFiles.size() + " 个日志文件");// 处理每个日志文件for (File logFile : logFiles) {try {processLogFile(connid, logFile, resultDir);} catch (Exception e) {System.err.println("处理文件 " + logFile.getName() + " 时发生错误: " + e.getMessage());e.printStackTrace();}}LogmnrDll.deinitLogmnr();} catch (NumberFormatException e) {System.err.println("错误: 数据库端口必须是数字");printUsage();} catch (Exception e) {System.err.println("错误: " + e.getMessage());e.printStackTrace();}}
}
window下运行
window跟linux环境下配置方式基本一致,因Logmnr接口底层走的是C接口,上面的目录结构及官方示例可以看出来 连接数据库时走的不是jdbc接口而是DM的dpi接口。
第一种方式 :window环境下需要配置dpi库跟Logmnr库的环境变量,注:较新的DM版本已将dpi依赖库独立到 dmdbms\drivers\dpi下这样可以不添加bin目录。
第二种方式 :程序运行前在启动类的VM options参数 配置DLL的路径
DM调用的dll在达梦安装目录的三个目录里面都需要引入
-Djava.library.path=D:\dmdbms\bin;D:\dmdbms\drivers_1_4_80\logmnr; -Dfile.encoding=UTF-8
注:项目中com.dameng.floader.jar 跟 com.dameng.logmnr.jar 不能同时使用,函数冲突后会报下面的这样的错。
Exception in thread "main" java.lang.NoSuchMethodError: com.dameng.JNIUtil.loadDmLibrarys(Ljava/lang/String;)Vat com.dameng.logmnr.LogmnrDll.<clinit>(LogmnrDll.java:18)at com.dameng.LogmnrTest.main(LogmnrTest.java:16)
将需要解析的归档放到同级的arch_file目录下,或者指定参数的时候指定要解析的归档目录,随后启动程序。
使用方法: java LogmnrTest <数据库IP> <数据库端口> <数据库用户名> <数据库密码> [日志目录路径]
注: 如果不指定日志目录路径,将使用程序同级目录下的arch_file目录示例: java LogmnrTest 192.168.112.168 5236 SYSDBA SYSDBA
示例: java LogmnrTest 192.168.112.168 5236 SYSDBA SYSDBA D:\dmdbms\arch_file
程序运行后,会在同级目录下的result下生成对应的解析归档后的数据。
linux编译运行
linux跟window的运行方式是一样的,配置环境变量后 指定参数运行程序即可。
1)目录结构如下 同时将要解析的归档放到arch_file目录下
[root@localhost LogmnrTest]# pwd
/opt/LogmnrTest
# 目录结构
├── LogmnrTest.java # 主程序 Java 源文件
├── result/ # 程序运行的输出结果目录
├── lib/ # Java 依赖的 Jar 包目录
│ └── com.dameng.logmnr.jar # 达梦 LogMnr 接口 Jar 包
├── arch_file/ # 存放归档日志的目录(LogMnr 分析目标)
│ └── ARCHIVE_LOCAL_0x4B8158F0_EP0_2025-05-26_16-04-49.log
2)临时指定当前用户的环境变量
[root@localhost LogmnrTest]# export LD_LIBRARY_PATH=/opt/dmdbms/bin:/opt/dmdbms/drivers/logmnr:$LD_LIBRARY_PATH
3)编译并运行java文件
[root@localhost LogmnrTest]# javac -cp "lib/*" LogmnrTest.java
[root@localhost LogmnrTest]# java -cp ".:lib/*" -Djava.library.path=/opt/dmdbms/bin:/opt/dmdbms/drivers/logmnr LogmnrTest 192.168.112.168 5236 SYSDBA SYSDBA
程序运行后会有对应的提示。
附录
no zlib in java.library.path
程序时报错:no zlib in java.library.path,
Exception in thread “main” java.lang.UnsatisfiedLinkError: no zlib in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1864)at java.lang.Runtime.loadLibrary0(Runtime.java:870)at java.lang.System.loadLibrary(System.java:1122)at com.dameng.JNIUtil.loadLibrary(JNIUtil.java:210)at com.dameng.logmnr.LogmnrDll.<clinit>(LogmnrDll.java:17)at com.dameng.App.main(App.java:18)
解决方案:zlib库在dmdbms/bin目录下有个zlib.dll,可以将这个库拷贝到drivers\logmnr路径下,或者将dmdbms/bin添加到环境变量下
no libeay32 in java.library.path
程序时报错:no libeay32 in java.library.path
Exception in thread "main" java.lang.UnsatisfiedLinkError: no libeay32 in java.library.pathat java.lang.ClassLoader.loadLibrary(ClassLoader.java:1864)at java.lang.Runtime.loadLibrary0(Runtime.java:870)at java.lang.System.loadLibrary(System.java:1122)at com.dameng.JNIUtil.loadLibrary(JNIUtil.java:210)at com.dameng.JNIUtil.loadSSLLibrary(JNIUtil.java:184)at com.dameng.logmnr.LogmnrDll.<clinit>(LogmnrDll.java:16)at com.dameng.App.main(App.java:18)
解决方案:可以换个较新的driver版本logmnr\dependencies下有libeay32.dll等依赖库 或者将dmdbms/bin添加到环境变量下
java.lang.UnsatisfiedLinkError: /opt/dmdbms/bin/libdmlogmnr_client.so: libdmdpi.so: 无法打开共享对象文件: 没有那个文件或目录
Exception in thread "main" java.lang.UnsatisfiedLinkError: /opt/dmdbms/bin/libdmlogmnr_client.so: libdmdpi.so: 无法打开共享对象文件: 没有那个文件或目录at java.lang.ClassLoader$NativeLibrary.load(Native Method)at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1934)at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1850)at java.lang.Runtime.loadLibrary0(Runtime.java:871)at java.lang.System.loadLibrary(System.java:1124)at com.dameng.JNIUtil.loadLibrary(JNIUtil.java:210)at com.dameng.JNIUtil.loadDmLibrarys(JNIUtil.java:261)at com.dameng.JNIUtil.loadDmLibrarys(JNIUtil.java:228)at com.dameng.logmnr.LogmnrDll.<clinit>(LogmnrDll.java:19)at LogmnrTest.main(LogmnrTest.java:17)
# 说明 Java 已经成功找到了 libdmlogmnr_client.so,但这个 .so 又依赖另一个动态库 libdmdpi.so,系统没找到这个依赖库。
问题说明:Java 已经成功找到了 libdmlogmnr_client.so,但这个 .so 又依赖另一个动态库 libdmdpi.so,系统没找到这个依赖库。
解决方案:给当前运行用户临时指定环境变量。
[root@localhost LogmnrTest]# export LD_LIBRARY_PATH=/opt/dmdbms/bin:/opt/dmdbms/drivers/logmnr:$LD_LIBRARY_PATH^C