Ikuta Erika

导航

Mongodb到mysql数据库的数据迁移(Java,Windows)

运行环境为windows

 

测试过260万的数据表,迁移大概要10分钟左右,当然肯定和网络,字段大小什么的有关系。

遇到的坑和注意点都用紫色标记了

 

PS:第一次写这么长的东西

一、Mongodb导出命令mongoexport

本地安装Mongodb,在安装目录的/bin下按住shift并右键“在此处打开命令窗口”,可执行以下语句进行导出。

mongoexport -h <ip:port> -d <database> -c <collection> -u <username> -p <password> --type <json/csv> -f <fileds> -o <outputfile> --limit %d --skip %d --noHeaderLine

  -h  host,主机ip+port

  -d  database,数据库名

  -c  collection,集合名(表名)

  -u  username,用户名

  -p  password,密码

  --type 导出类型 json/csv

  -f  当type为csv时必选,导出字段名,逗号分隔

  -o  outputfile,输出文件名

  -q  query,查询参数,为json字符串

  --sort 排序参数,为json字符串

  --limit 返回结果数,和skip分页时使用

  --skip 跳过的记录数

  --noHeaderLine 导出文件不包含首行字段名

示例:

mongoexport -h 10.10.10.10:27027 -d test -c Student -u mydb -p mydb --type csv -f "_id,stuno,stuname,age,sex" -o D:/Student.csv --limit 1000 --skip 0 -q {'stuno':'stu_11123'} --sort {age:1} --noHeaderLine

二、MySQL导入命令mysqlimport

本地安装MySQL,在安装目录的/bin下按住shift并右键“在此处打开命令窗口”,可执行以下语句进行导入。

mysqlimport -h <hostname> -P <port> -u <username> -p<password> --local <databasename> <importfile> -c <colums> --fields-terminated-by=, --fields-enclosed-by=\" --lines-terminated-by=\r\n --ignore-lines=1

-h  hostname
-P  port
-u  username
-p   password, 密码字符串和-p之间没有空格
--local   使用本地的文件导入
databasename  数据库名
importfile  导入文件路径,文件名被认为是表名,如下例中的Student
-c  colums,文件中字段分割顺序,用逗号分割
--fields-terminated-by=字符串  设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值为制表符“\t”。
--fields-enclosed-by=字符  设置字符来括住字段的值,只能为单个字符。
--fields-optionally-enclosed-by=字符  设置字符括住CHAR、VARCHAR和TEXT等字符型字段,只能为单个字符。
--fields-escaped-by=字符  设置转义字符,默认值为反斜线“\”。
--lines-terminated-by=字符串  设置每行数据结尾的字符,可以为单个或多个字符,默认值为“\n”。
--ignore-lines=n  表示可以忽略前n行。可以用来跳过首行的字段名。

示例:

mysqlimport -h 10.10.10.10 -P 3306 -u dbtest -pdbtest --local mydb D:/Student.txt -c  "id,stuno,stuname,age,sex" --fields-terminated-by=, --fields-enclosed-by=\" --lines-terminated-by=\r\n

三、Java运行cmd命令工具类

package util;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.commons.lang.StringUtils;

/**
 * 
 * Cmd命令執行工具
 *
 * @author  2018.03.14
 */
public class CmdUtil {
    private final static CmdUtil instance = new CmdUtil();

    private final static Runtime cmd = Runtime.getRuntime();
    
    public static CmdUtil getInstance() {
        return instance;
    }
    
    private CmdUtil() {
        
    }
    
    /**
     * 
     * 用于执行cmd命令并返回执行结果
     *
     * @param commondStr 命令字符串
     * @param dir 执行目录
     * @return 执行结果
     */
    public String exec(String commondStr, File dir) {
        //System.out.println(">" + commondStr);
        StringBuilder sb = new StringBuilder();
        try {
            // "/c"代表程序执行有参数,如果不加上就直接运行了cmd.exe; dir是程序执行目录
            Process process = cmd.exec("cmd.exe /c" + commondStr, null, dir);
            //接收执行结果字符串
            String temp = "";
            BufferedReader errBr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            while (StringUtils.isNotEmpty(temp = errBr.readLine())) {
                sb.append(temp).append("\r\n");
            }
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
            while (StringUtils.isNotEmpty(temp = br.readLine())) {
                sb.append(temp).append("\r\n");
            }
            br.close();
            errBr.close();
        } catch (IOException e) {
            e.printStackTrace();
        } 
        //System.out.println(sb.toString());
        return sb.toString();
    }
}

四、文本文件处理工具类

在导入mysql数据库前,需要对文件进行一些操作,这里提供了对文件每一行字符进行处理并生成指定文件的类

package util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 
 * 文本操作工具
 *
 * @author  2018.03.14
 */
public class TextFileUtil {
    private static final TextFileUtil instance = new TextFileUtil();

    private TextFileUtil() {

    }

    public static TextFileUtil getInstance() {
        return instance;
    }

    /**
     * 复制文件并处理每一行的字符串
     *
     * @param srcFile 源文件
     * @param target 目标文件
     * @param opertor 每行的字符串处理类
     */
    public void transferLine(String srcFile, String target, OpertorInter opertor) {
        if (srcFile.equals(target)) {
            System.out.println("Warning : src file is same to target file");
            return;
        }
        FileReader fr = null;
        BufferedReader br = null;

        FileWriter fw = null;
        BufferedWriter bw = null;

        String temp = null;
        try {
            fr = new FileReader(srcFile);
            br = new BufferedReader(fr);

            fw = new FileWriter(target);
            bw = new BufferedWriter(fw);

            while (null != (temp = br.readLine())) {
                bw.write(opertor.transferLine(temp));
                bw.newLine();
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != br && null != fr) {
                try {
                    br.close();
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    br = null;
                    fr = null;
                }
            }
            if (null != bw && null != fw) {
                try {
                    bw.close();
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    bw = null;
                    fw = null;
                }
            }
        }

    }

}

 

其中OpertorInter接口方法transferLine(str)提供了对每行数据的处理操作,这里是接口和其实现类:

package util;

public interface OpertorInter {
    String transferLine(String before);
}

 

package util;

/**
 * 对每条记录进行处理
 *
 * @author  2018.03.14
 */
public class ObjectIdOpertor implements OpertorInter {

    public String transferLine(String before) {
        // 将空值设置为\N,否则在mysqlimport时会将空值的日期字段设置为0000-00-00 00:00:00
        //首先是行尾的空值
        before = before.replaceFirst(",$", ",\\\\N");
        //再循环替换所有空值
        String temp = "";
        while (!temp.equals(before)) {
            temp = before;
            before = temp.replaceFirst(",,", ",\\\\N,");
        }
        
        //mongodb导出文件中_id字段转换为字符串,只有ObjectId类型时才需要转换
        if (before.startsWith("ObjectId")) {
            return before.substring(9, 33) + before.substring(34);
        }
        return before;
    }
}

 

五、Main方法

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import util.CmdUtil;
import util.ObjectIdOpertor;
import util.OpertorInter;
import util.TextFileUtil;

/**
 * 用于将mongodb中的表数据迁移到mysql数据库中
 * @author  2018.03.14
 */
public class DBTransferTest {
    public static void main(String[] args) {
        // mongodb中集合字段,导出的文件以此为准
        Map<String, String> fileds = new HashMap<String, String>();
        fileds.put("Student", "_id,stuno,stuname,age,sex");

        // mongo数据库信息
        String host = "10.10.10.10:27027";
        String database = "test";
        String userName = "mydb";
        String password = "mydb";
        // mysql数据库信息
        String hostForMysql = "10.10.10.10";
        int portForMysql = 3306;
        String userForMysql = "dbtest";
        String passForMysql = "dbtest";
        String databaseForMysql = "mydb";
        
        // cmd命令運行
        // mongodb运行目录
        File dir = new File("D:/Program Files/MongoDB/Server/3.6/bin");
        // mysql运行目录
        File mySqlDir = new File("D:/Program Files/mysql-5.6.39-winx64/bin");
        // mongodb导出命令 mongoexport -h <ip:port> -d <database> -c <collection> -u <username> -p <password> --type <json/csv> -f <fileds> -o <outputfile> --limit %d --skip %d
        // --noHeaderLine 不输出列名
        String exportSrcCmd = "mongoexport -h %s -d %s -c %s -u %s -p %s --type csv -f %s -o %s --limit %d --skip %d --noHeaderLine";
        // mysql导入命令 
     // mysqlimport -h <ip> -P <port> -u <username> -p<password> --local <database> <inputfile> -c <columes> --fields-terminated-by=\\, --fields-enclosed-by=\\\" --lines-terminated-by=\\n --ignore-lines=1
// --ignore-lines=1 忽略首行的列名,上面导出时已忽略,这里就不再跳过第一行了 String importSrcCmd = "mysqlimport -h %s -P %d -u %s -p%s --local %s %s -c %s --fields-terminated-by=, --fields-enclosed-by=\\\" --lines-terminated-by=\\r\\n"; // 匹配命令执行结果中的导入导出数 String regForExport = "(\\d+) record"; String regForImport = "Records: (\\d+)"; // 分页导出的每页数据量大小 int limit = 10000; // 分页参数 int skip = 0; // 由于mongodb导出的文件中_id字段 OpertorInter opertor = new ObjectIdOpertor(); long st = System.currentTimeMillis(); for (Map.Entry<String, String> en : fileds.entrySet()) { // 表名 String collection = en.getKey(); // 获取两数据库表的字段名 当前只有 _id --> id 不同 String filed = fileds.get(collection); String filedForMysql = filed.substring(1); // mongodb导出文件名 String output = "D:/" + collection + ".csv"; // mysql要导入的文件名,此处文件名决定了mysqlimport要导入的表名 String importFile = "D:/" + collection + ".txt"; long startTime = System.currentTimeMillis(); System.out.println("***********" + collection + "***********"); for (int pageNo = 0;; pageNo++) { long pageSt = System.currentTimeMillis(); // pageNo这里从0开始 skip = pageNo * limit; // mongoexport System.out.print("----" + (skip + 1) + "~" + (skip + limit) + ":[Exporting:"); String exportCmd = String.format(exportSrcCmd, host, database, collection, userName, password, filed, output, limit, skip); int exportNum = parseOperateNum(CmdUtil.getInstance().exec(exportCmd, dir), regForExport); System.out.print(exportNum); // 处理文件将_id字段的Object()去除,只保留string类型的id TextFileUtil.getInstance().transferLine(output, importFile, opertor); // mysqlimport System.out.print("][Importing:"); String importCmd = String.format(importSrcCmd, hostForMysql, portForMysql, userForMysql, passForMysql, databaseForMysql, importFile, filedForMysql); String reString = CmdUtil.getInstance().exec(importCmd, mySqlDir); int importNum = parseOperateNum(reString, regForImport); System.out.print(importNum + "]"); long pageEd = System.currentTimeMillis(); System.out.println("[Cost time:" + (pageEd-pageSt) + "ms]"); if (exportNum != importNum) { System.out.println("Error and Break: importNum is not same to exportNum--" + reString); break; } // 如果导出数量为0或者导出数量还不到一页,则没有下一页 if (limit > exportNum || 0 == exportNum) { System.out.println("----No records, export end."); break; } } long endTime = System.currentTimeMillis(); System.out.println("Cost time : " + (endTime - startTime) + "ms\r\n"); } long et = System.currentTimeMillis(); System.out.println("***********TOTAL COST TIME : " + (et - st) / 60000f + "min***********"); } /** * * 获取匹配到的字符并转为int * 这里用于获取cmd处理结果中的导入导出记录数,以判断是否最后一页数据 * * @param re * @param reg * @return */ public static int parseOperateNum(String re, String reg) { Pattern p = Pattern.compile(reg); Matcher m = p.matcher(re); if (m.find()) { return Integer.parseInt(m.group(1)); } return 0; } }

 

posted on 2018-03-15 10:35  风灯  阅读(862)  评论(0编辑  收藏  举报