达梦数据库dmfldr快速数据装载工具从控制台操作到jni编程问题总结(windows+linux)

1. 准备工作

在开始之前,请确保以下准备工作已完成:

  • dmfldr工具位于bin目录中。
  • 目标数据库表已经创建,并且准备好待使用的控制文件。
  • 如果是导入需要准备好数据文件,注意在 Windows 和 Linux 环境中,换行符是不同的。Windows使用CRLF(回车换行),而Linux使用LF(换行)。尽管它们都是 UTF-8 编码,但是换行符的不同可能会导致导入出现数据格式错误、缺行等问题。因此,在准备数据文件时,请确保使用正确的换行符。可以使用编辑器(如IntelliJ IDEA)进行格式转换,以确保数据文件的格式正确,避免导入时出现意外问题。

控制文件是dmfldr工具的关键输入之一,它指示dmfldr如何处理数据文件。以下是一个简单的控制文件示例:

LOAD DATA
INFILE 'D:/DMDBMS/bin/test/out.txt' //代表导入或导出的数据文件路径
INTO TABLE TEST.TEST3 //导入或导出的表
FIELDS '|' //设置分割符

需要确保控制文件中数据文件路径和表名正确,并根据需要设置正确的分隔符,注意数据库表的大小写,假如表名是小写,需加上双引号,如模式名."表名",否则无法识别到数据库对象。

2. 控制台指令

dmfldr 的使用较为灵活,参数较多,字符串类型参数必须以引号封闭。具体参数如下:

  • USERID: 用户名/口令格式为USER/PWD@SERVER:PORT#SSL_PATH@SSL_PWD。

  • CONTROL: 控制文件的路径。

  • LOG: 日志文件的路径,默认为fldr.log。

  • BADFILE: 错误数据记录文件的路径,默认为fldr.bad。

  • SKIP: 初始忽略逻辑行数,默认为0。

  • LOAD: 需要装载的行数,默认为ALL。

  • ROWS: 提交频次,默认为50000,DIRECT为FALSE时有效。

  • DIRECT: 是否使用快速方式装载,默认为TRUE。

  • SET_IDENTITY: 是否插入自增列,默认为FALSE。

  • SORTED: 数据是否已按照聚集索引排序,默认为FALSE。

  • INDEX_OPTION: 索引选项,默认为1。

  • ERRORS: 允许的最大数据错误数,默认为100。

  • CHARACTER_CODE: 字符编码,默认为GBK,可选值包括GBK、GB18030、UTF-8、SINGLE_BYTE、EUC-KR。

  • MODE: 装载方式,IN表示载入,OUT表示载出,默认为IN。

  • CLIENT_LOB: 大字段目录是否在本地,默认为FALSE。

  • LOB_DIRECTORY: 大字段数据文件存放目录。

  • LOB_FILE_NAME: 大字段数据文件名称,仅导出有效,默认为dmfldr.lob。

  • BUFFER_NODE_SIZE: 读入文件缓冲区的大小,默认为10,有效值范围1~2048。

  • READ_ROWS: 工作线程一次最大处理的行数,默认为100000,最大支持2^26-10000。

  • NULL_MODE: 载入时NULL字符串是否处理为NULL,默认为FALSE。

  • NULL_STR: 载入时视为NULL值处理的字符串。

  • SEND_NODE_NUMBER: 运行时发送节点的个数,默认为20,有效值范围16~65535。

  • TASK_THREAD_NUMBER: 处理用户数据的线程数目,默认与处理器核数量相同,有效值范围1~128。

  • BLDR_NUM: 服务器BLDR数目,默认为64,有效值范围1~1024。

  • BDTA_SIZE: bdta的大小,默认为5000,有效值范围100~10000。

  • COMPRESS_FLAG: 是否压缩bdta,默认为FALSE。

  • MPP_CLIENT: MPP环境是否本地分发,默认为TRUE。

  • SINGLE_FILE: MPP环境是否只生成单个数据文件,默认为FALSE。

  • LAN_MODE: MPP环境是否以内网模式装载数据,默认为FALSE。

  • UNREP_CHAR_MODE: 非法字符处理选项,默认为0。

  • SILENT: 是否静默方式装载数据,默认为FALSE。

  • BLOB_TYPE: BLOB类型字段数据值的实际类型,默认为HEX_CHAR,仅在DIRECT=FALSE有效。

  • OCI_DIRECTORY: OCI动态库所在的目录。

  • DATA: 指定数据文件路径。

  • ENABLE_CLASS_TYPE: 是否允许用户导入CLASS类型数据,默认为FALSE。

  • FLUSH_FLAG: 提交时是否立即刷盘,默认为FALSE。

  • IGNORE_BATCH_ERRORS: 是否忽略错误数据继续导入,默认为FALSE。

  • SINGLE_HLDR_HP: 是否使用单个HLDR装载HUGE水平分区表,默认为FALSE。

  • EP: 指定需要发送数据的站点序号列表,仅向MPP环境导入数据时有效。

  • HELP: 打印帮助信息。

USERID 和 CONTROL是启动 dmfldr 必须要指定的参数,且 USERID 必须是第一个参数,CONTROL 必须是第二个参数。控制文件通常以ctl为后缀,简单版如下:

Windows 控制台指令:

dmfldr.exe SYSDBA/SYSDBA@localhost:5236 control='D:\DMDBMS\bin\test\out.ctl' mode='out'

在打开控制台时注意Windows不能直接用右键打开控制台,而要通过win+r打开运行输入cmd打开控制台,否则就会出现无法识别dmfldr.exe的情况,就算加上./dmfldr.exe也会在控制文件处报错,如下图:

 右键打开控制台反例1
右键打开控制台反例2

Linux控制台指令:

./dmfldr userId=SYSDBA/SYSDBA@localhost:5238 control=\'/home/dmdba/out.ctl\' mode=\'out\'

3. Java代码方式

目标是实现多线程将多个有大字段的文本数据文件导入数据库表中,特别注意单表不能多线程,多线程指的同时导入不同的表。

方法一:Process方式

如果需要在Java代码中调用dmfldr工具,可以使用Process类来执行命令。请确保在执行命令时处理输出流,以避免进程阻塞的情况发生。示例代码如下:

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main1 {
    //private static final String DMFLDR_PATH = "/home/ct/dmfldrTest/bin/dmfldr userId=SYSDBA/SYSDBA@192.168.80.10:5238";
    //private static final String controlFilePath = "/home/ct/dmfldrTest";
    // 动态生成控制文件内容
    //private static final String controlFileContent = "LOAD DATA\n" + "INFILE '/home/ct/dmfldrTest" ;
    private static final String DMFLDR_PATH = "D:/DMDBMS/bin/dmfldr.exe userId=SYSDBA/SYSDBA@192.168.80.10:5238";
    private static final String controlFilePath = "D:/DMDBMS/bin/test/";
    // 动态生成控制文件内容
    private static final String controlFileContent = "LOAD DATA\n" + "INFILE 'D:/DMDBMS/bin/test/" ;

    @NoArgsConstructor
    @AllArgsConstructor
    static class DMFldrTest implements Callable<String> {
        private String tableName;//表名
        @Override
        public String call() throws Exception {
            // 写入控制文件
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(controlFilePath+tableName + "_in.ctl"))) {
                writer.write(controlFileContent + tableName + ".txt'\n" + "INTO TABLE TEST."+tableName+ "\n" + "FIELDS '|'");
            } catch (IOException e) {
                e.printStackTrace();
                return "表 " + tableName + " 导入失败";
            }
            try {
                // dmfldr导入命令
                String importCommand = DMFLDR_PATH + " control='" + controlFilePath + tableName + "_in.ctl' mode='in'";
                System.out.println(importCommand);
                // 执行导入命令
                Process process = Runtime.getRuntime().exec(importCommand);
                // 读取错误流
                BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream(),"GBK"));
                StringBuffer errorLog = new StringBuffer();
                Pattern pattern = Pattern.compile("load success.*?(\\n.*?){4}");

                String line;
                while ((line = errorReader.readLine()) != null) {
                    errorLog.append(line).append("\n");
                }
                Matcher matcher = pattern.matcher(errorLog.toString());
                // 等待导入完成
                int exitCode = process.waitFor();
                if (matcher.find()) {
                    return matcher.group();
                } else {
                    pattern = Pattern.compile("load failed.*?(\\n.*?){4}");
                    matcher = pattern.matcher(errorLog.toString());
                    if (matcher.find()) {
                        return matcher.group();
                    }
                    else{
                        return "表 " + tableName + " 导入失败";
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                return "表 " + tableName + " 导入失败";
            }
        }
    }
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3); // 创建线程池
        DMFldrTest task1 = new DMFldrTest("RATEDCDR");
        DMFldrTest task2 = new DMFldrTest("RATEDCDR");
        DMFldrTest task3 = new DMFldrTest("CYCLEBILL");

        Future<String> future1 = executor.submit(task1); // 提交任务1到线程池
        Future<String> future2 = executor.submit(task2); // 提交任务2到线程池
        Future<String> future3 = executor.submit(task3); // 提交任务3到线程池

        try {
            String result1 = future1.get(); // 获取任务1的执行结果
            String result2 = future2.get(); // 获取任务2的执行结果
            String result3 = future3.get(); // 获取任务2的执行结果
            System.out.println(result1);
            System.out.println(result2);
            System.out.println(result3);
        } catch (Exception e) {
            e.printStackTrace();
        }
        executor.shutdown(); // 关闭线程池
    }
}

 打印结果:

在处理大量数据时,可能会出现程序阻塞的情况,尤其是在调用exec(importCommand)后的process.waitFor()方法处。这是由于执行dmfldr命令时产生了输出流,而该输出流没有被正确消耗,导致进程挂起。为了避免这种情况,可以通过重定向输出流到另一个流,并及时消耗这个流的内容。这可以通过为exec()方法提供另一个ProcessBuilder对象进行配置来实现。这样,即使输出流产生了大量内容,也能够及时处理,避免进程挂起的情况发生。

方法二:达梦官方JNI接口

使用达梦官方提供的JNI接口,需要注意以下几点:

  • 依赖获取:达梦提供的jar包不是公开依赖,因此你需要从达梦的特定目录下获取。在Windows和Linux系统下,所需的jar包可能不同,请确保从达梦的正确目录获取所需的jar包。

  • 环境变量设置:无论Windows或Linux系统都需要提前设置bin目录的环境变量才能正常运行程序,在Linux系统下,可以通过使用export LD_LIBRARY_PATH=$PATH:/home/boss/dm/ct-test/bin/命令。确保正确设置环境变量,以便程序能够正确地访问所需的库文件。

  • 设置线程数和文件编码:在使用达梦官方JNI接口时特别需要注意设置线程数和文件编码,以避免识别到文件但无法导入数据的情况。确保根据实际需求设置合适的线程数和正确的文件编码,以确保数据能够顺利导入到达梦数据库中。

示例代码 :

import com.dameng.floader.Instance;
import com.dameng.floader.Properties;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.io.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestFloderWindow {

    /*
    * 获取文件编码格式
     */
    public static String getFileCharsetName(String fileName) throws IOException {
        InputStream inputStream = new FileInputStream(fileName);
        byte[] head = new byte[3];
        inputStream.read(head);

        String charsetName = "GBK";//或GB2312,即ANSI
        if (head[0] == -1 && head[1] == -2 ) //0xFFFE
            charsetName = "UTF-16";
        else if (head[0] == -2 && head[1] == -1 ) //0xFEFF
            charsetName = "Unicode";//包含两种编码格式:UCS2-Big-Endian和UCS2-Little-Endian
        else if(head[0]==-27 && head[1]==-101 && head[2] ==-98)
            charsetName = "UTF-8"; //UTF-8(不含BOM)
        else if(head[0]==-17 && head[1]==-69 && head[2] ==-65)
            charsetName = "UTF-8"; //UTF-8-BOM
        inputStream.close();
        //System.out.println(code);
        return charsetName;
    }

    /**********自定义init函数,设置属性信息,并初始化当前快速装载实例**********/
    public boolean init(Instance instance, int threadNum, String tableName)
    {
        // 分配句柄
        instance.allocInstance();
        // 设置必要的属性信息
        String host = "192.168.80.1";
        String port = "5238";
        String userName = "SYSDBA";
        String password = "SYSDBA";
        instance.setAttribute(Properties.FLDR_ATTR_SERVER, host);
        instance.setAttribute(Properties.FLDR_ATTR_PORT, port);
        instance.setAttribute(Properties.FLDR_ATTR_UID, userName);
        instance.setAttribute(Properties.FLDR_ATTR_PWD, password);
        // 其余属性用户均可根据实际情况选择性设置
        // 设置编码
        //System.out.println(System.getProperty("file.encoding"));
        try {
            System.out.println(getFileCharsetName("D:/DMDBMS/bin/test/" + tableName + ".txt"));
            instance.setAttribute(Properties.FLDR_ATTR_DATA_CHAR_SET, getFileCharsetName("D:/DMDBMS/bin/test/" + tableName + ".txt"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 设置是否插入自增列
        //instance.setAttribute(Properties.FLDR_ATTR_SET_INDENTITY, "0");
        // fldr task线程默认为cpu个数,如果客户端线程数大于cpu个数,则必须设置该参数
        instance.setAttribute(Properties.FLDR_ATTR_TASK_THREAD_NUM, String.valueOf(threadNum));
        // 记录出错的信息
        String dmHome = System.getProperty("DM_HOME");
        if(dmHome != null && !dmHome.equals("")){
            if(dmHome.endsWith("/") || dmHome.endsWith("\\")){
                instance.setAttribute(Properties.FLDR_ATTR_BAD_FILE, dmHome + "BADFILE_2.TXT");
            }else{
                instance.setAttribute(Properties.FLDR_ATTR_BAD_FILE, dmHome + File.separator + "BADFILE_2.TXT");
            }
        }
        // 装载日志
        if (dmHome != null && !dmHome.equals("")){
            if (dmHome.endsWith("/") || dmHome.endsWith("\\")){
                instance.setAttribute(Properties.FLDR_ATTR_LOG_FILE, dmHome + "FLDRLOG_2.TXT");
            }else{
                instance.setAttribute(Properties.FLDR_ATTR_LOG_FILE, dmHome + File.separator + "FLDRLOG_2.TXT");
            }
        }
        //初始化当前快速装载实例
        boolean success = instance.initializeInstance(null, null, null, "TEST."+tableName);
        return success;
    }

    @NoArgsConstructor
    @AllArgsConstructor
    static class DMFldrTest implements Callable<Long> {
        private String tableName;
        private int threadNum;
        @Override
        public Long call() throws Exception{
            Instance instance = new Instance();
            TestFloderWindow floder = new TestFloderWindow();
            boolean success = floder.init(instance, threadNum, tableName);
            //若快速装载实例初始化成功,则将数据文件内容载入至数据库中
            if(success){
                String ctl = "LOAD DATA\r\n" +
                        "INFILE 'D:/DMDBMS/bin/test/" + tableName + ".txt'\r\n" +
                        "INTO TABLE TEST." + tableName + "\r\n" +
                        "FIELDS '|'\r\n";
                System.out.println(ctl);
                //根据ctrl内容进行数据载入
                instance.setControl(ctl);
            }

            //若快速装载实例初始化失败,则打印错误信息,并释放快速装载实例
            if(!success){
                byte[] message = null;
                try {
                    message = instance.getErrorMsg().getBytes("utf-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                ;
                System.out.println("出错信息:" + message);
                instance.free();
                return 0L;
            }

            //快速装载完成,打印实际插入的行数,并释放快速装载实例
            else{
                instance.finish();
                // 返回实际插入的行数
                long rowHaveCopied = Long.parseLong(instance.getAttribute(Properties.FLDR_ATTR_COMMIT_ROWS));
                System.out.println("已复制行数:" + rowHaveCopied);
                instance.uninitialize();
                instance.free();
                return rowHaveCopied;
            }

        }
    }

    public static void main(String[] args)
    {
        int threadNum = 3;
        ExecutorService executor = Executors.newFixedThreadPool(threadNum);
        DMFldrTest task1 = new DMFldrTest("RATEDCDR",threadNum);
        DMFldrTest task2 = new DMFldrTest("RATEDCDR",threadNum);
        DMFldrTest task3 = new DMFldrTest("CYCLEBILL",threadNum);

        Future<Long> future1 = executor.submit(task1); // 提交任务1到线程池
        Future<Long> future2 = executor.submit(task2); // 提交任务2到线程池
        Future<Long> future3 = executor.submit(task3); // 提交任务3到线程池

        try {
            Long result1 = future1.get(); // 获取任务1的执行结果
            Long result2 = future2.get(); // 获取任务2的执行结果
            Long result3 = future3.get(); // 获取任务2的执行结果
            if(result1 == 0L){
                System.out.println("RATEDCDR导入异常");
            }
            else{
                System.out.println("RATEDCDR导入成功");
            }
            if(result2 == 0L){
                System.out.println("RATEDCDR导入异常");
            }
            else{
                System.out.println("RATEDCDR导入成功");
            }
            if(result3 == 0L){
                System.out.println("CYCLEBILL导入异常");
            }
            else{
                System.out.println("CYCLEBILL导入成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        executor.shutdown();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值