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也会在控制文件处报错,如下图:
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();
}
}