杰瑞科技汇

java oracle数据备份

核心思想

在 Java 中实现 Oracle 数据备份,主要有以下几种策略:

java oracle数据备份-图1
(图片来源网络,侵删)
  1. 数据导出/导入 (使用 expdp/impdp 命令):这是 Oracle 官方推荐的数据泵(Data Pump)技术,性能最高,功能最强大,Java 程序的作用是在后台调用操作系统的命令行来执行 expdp
  2. 直接生成 SQL 文件 (使用 spool):通过 JDBC 连接,查询数据,并逐行或批量地拼接成 INSERT 语句,写入到一个 .sql 文件中,这种方法灵活,但性能较低,适合小数据量或特定格式的备份。
  3. 生成 CSV/Excel 文件:与生成 SQL 类似,但将数据导出为结构化的文本文件(如 CSV)或 Excel 文件,这种方式便于在其他系统(如 Excel)中查看和分析。

使用 expdp 命令(推荐,性能最佳)

这是最常用、最高效的方法,Java 代码通过 Runtime.exec()ProcessBuilder 来执行 expdp 命令。

准备工作:配置目录对象 (Directory)

expdp 需要知道将导出文件(DMP 文件)存放在哪个操作系统的目录下,这个目录在 Oracle 中被抽象为一个“目录对象”。

*在 Oracle SQLPlus 或类似工具中执行:**

-- 假设你想将文件存放在 D:\oracle_backups 目录下
-- 首先确保该操作系统目录存在且有 Oracle 用户写入权限
-- 创建目录对象
CREATE DIRECTORY exp_dir AS 'D:\oracle_backups';
-- 授予用户(scott)对该目录的读写权限
GRANT READ, WRITE ON DIRECTORY exp_dir TO scott;

Java 代码实现

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
public class OracleExpdpBackup {
    public static void main(String[] args) {
        // --- 配置信息 ---
        String dbUser = "scott";
        String dbPassword = "tiger";
        String dbName = "orclpdb1"; // TNS 名称或服务名
        String tableName = "emp";   // 要备份的表名,可以为空则备份整个 schema
        String directoryObject = "EXP_DIR"; // Oracle 中创建的目录对象名
        String dumpFileName = "emp_backup_" + System.currentTimeMillis() + ".dmp";
        String logFileName = "emp_backup_" + System.currentTimeMillis() + ".log";
        // expdp 命令
        // 格式: expdp [username]/[password]@[db_service] DUMPFILE=[filename.dmp] LOGFILE=[filename.log] DIRECTORY=[directory_object] TABLES=[table_name]
        String command = String.format(
            "expdp %s/%s@%s DUMPFILE=%s LOGFILE=%s DIRECTORY=%s TABLES=%s",
            dbUser, dbPassword, dbName, dumpFileName, logFileName, directoryObject, tableName
        );
        System.out.println("Executing command: " + command);
        try {
            // 使用 ProcessBuilder 更安全地执行命令
            ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", command);
            pb.directory(new File("D:\\oracle_backups")); // 设置工作目录,与 DIRECTORY 对应
            Process process = pb.start();
            // 读取命令的输出流,可以实时查看 expdp 的进度
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            // 读取错误流
            BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            while ((line = errorReader.readLine()) != null) {
                System.err.println(line);
            }
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                System.out.println("Backup completed successfully!");
            } else {
                System.err.println("Backup failed with exit code: " + exitCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码说明:

java oracle数据备份-图2
(图片来源网络,侵删)
  • ProcessBuilder: 比 Runtime.exec() 更推荐,因为它可以更好地处理命令参数和路径。
  • cmd.exe /c: 在 Windows 系统下,cmd.exe /c 会执行后面的命令然后关闭,在 Linux/macOS 下,应该使用 /bin/sh -c
  • pb.directory(new File(...)): 这一步非常重要,它设置了 expdp 进程的当前工作目录,确保 DMPLOG 文件能正确生成在指定的物理路径下。
  • process.getInputStream(): expdp 会将执行过程中的信息(如进度)输出到标准输出流,通过读取它可以监控备份过程。
  • process.getErrorStream(): 如果命令执行失败(例如密码错误、目录无权限),错误信息会输出到这里。
  • process.waitFor(): 阻塞当前线程,直到 expdp 进程执行完毕。

生成 SQL 文件(适合小数据量或特定需求)

这种方法不依赖 expdp,完全通过 JDBC 实现,它将数据查询出来,然后格式化为 INSERT 语句写入文件。

Java 代码实现

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
public class OracleSqlBackup {
    public static void main(String[] args) {
        String dbUrl = "jdbc:oracle:thin:@localhost:1521:orclpdb1";
        String dbUser = "scott";
        String dbPassword = "tiger";
        String tableName = "emp";
        String sqlFilePath = "D:\\oracle_backups\\emp_backup.sql";
        // JDBC 连接和资源
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        BufferedWriter writer = null;
        try {
            // 1. 加载驱动 (对于新版JDBC驱动,通常可以省略)
            Class.forName("oracle.jdbc.driver.OracleDriver");
            // 2. 获取连接
            conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
            // 3. 创建语句
            stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            rs = stmt.executeQuery("SELECT * FROM " + tableName);
            // 4. 获取结果集的元数据(列名、类型等)
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            // 5. 创建文件写入器
            writer = new BufferedWriter(new FileWriter(sqlFilePath));
            // 6. 写入 SQL 文件头
            writer.write("-- Backup of table: " + tableName + "\n");
            writer.write("-- Generated at: " + new java.util.Date() + "\n\n");
            // 7. 遍历结果集,生成 INSERT 语句
            while (rs.next()) {
                StringBuilder insertStatement = new StringBuilder("INSERT INTO " + tableName + " (");
                // 构建列名部分
                for (int i = 1; i <= columnCount; i++) {
                    insertStatement.append(metaData.getColumnName(i));
                    if (i < columnCount) {
                        insertStatement.append(", ");
                    }
                }
                insertStatement.append(") VALUES (");
                // 构建值部分
                for (int i = 1; i <= columnCount; i++) {
                    Object value = rs.getObject(i);
                    if (value == null) {
                        insertStatement.append("NULL");
                    } else if (metaData.getColumnType(i) == java.sql.Types.VARCHAR ||
                               metaData.getColumnType(i) == java.sql.Types.CHAR ||
                               metaData.getColumnType(i) == java.sql.Types.DATE ||
                               metaData.getColumnType(i) == java.sql.Types.TIMESTAMP) {
                        // 字符串和日期类型需要用单引号括起来,并处理内部的引号
                        String stringValue = value.toString().replace("'", "''");
                        insertStatement.append("'").append(stringValue).append("'");
                    } else {
                        insertStatement.append(value);
                    }
                    if (i < columnCount) {
                        insertStatement.append(", ");
                    }
                }
                insertStatement.append(");\n");
                // 写入文件
                writer.write(insertStatement.toString());
            }
            System.out.println("SQL backup file generated successfully at: " + sqlFilePath);
        } catch (ClassNotFoundException | SQLException | IOException e) {
            e.printStackTrace();
        } finally {
            // 8. 关闭所有资源 (非常重要!)
            try {
                if (writer != null) writer.close();
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (IOException | SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

代码说明:

  • ResultSetMetaData: 用于获取结果集的列信息(列名、数据类型等),这是动态构建 INSERT 语句的关键。
  • SQL 注入和转义: 在拼接 SQL 值时,必须正确处理字符串和日期类型,特别是要对单引号进行转义( -> ),以避免 SQL 注入和语法错误。
  • 性能: 这种方法逐行处理数据,对于千万级甚至百万级的数据表,性能会非常差,并且会消耗大量内存。

生成 CSV 文件(通用性强)

这种方法与生成 SQL 类似,但输出格式是 CSV,可以被 Excel、数据库等工具轻松导入。

Java 代码实现

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
public class OracleCsvBackup {
    public static void main(String[] args) {
        String dbUrl = "jdbc:oracle:thin:@localhost:1521:orclpdb1";
        String dbUser = "scott";
        String dbPassword = "tiger";
        String tableName = "emp";
        String csvFilePath = "D:\\oracle_backups\\emp_backup.csv";
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        BufferedWriter writer = null;
        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
            stmt = conn.createStatement();
            rs = stmt.executeQuery("SELECT * FROM " + tableName);
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            writer = new BufferedWriter(new FileWriter(csvFilePath));
            // 1. 写入 CSV 文件头(列名)
            for (int i = 1; i <= columnCount; i++) {
                writer.append(metaData.getColumnName(i));
                if (i < columnCount) {
                    writer.append(",");
                }
            }
            writer.append("\n");
            // 2. 写入数据行
            while (rs.next()) {
                for (int i = 1; i <= columnCount; i++) {
                    Object value = rs.getObject(i);
                    if (value == null) {
                        writer.append("");
                    } else {
                        // CSV 规范:如果字段包含逗号、换行符或双引号,需要用双引号括起来
                        String stringValue = value.toString();
                        if (stringValue.contains(",") || stringValue.contains("\n") || stringValue.contains("\"")) {
                            writer.append("\"").append(stringValue.replace("\"", "\"\"")).append("\"");
                        } else {
                            writer.append(stringValue);
                        }
                    }
                    if (i < columnCount) {
                        writer.append(",");
                    }
                }
                writer.append("\n");
            }
            System.out.println("CSV backup file generated successfully at: " + csvFilePath);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) writer.close();
                if (rs != null) rs.close();
                if (stmt != null) stmt.close();
                if (conn != null) conn.close();
            } catch (IOException | SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

总结与对比

特性 方法一 (expdp) 方法二 (生成 SQL) 方法三 (生成 CSV)
性能 极高,数据泵直接在服务器端操作 ,逐行处理,网络和CPU开销大 ,逐行处理,网络和CPU开销大
数据量 适合任何大小,特别是大数据量 仅适合小数据量(万行以内) 仅适合小数据量(万行以内)
依赖性 需要 Oracle 客户端 (Data Pump 可选) 只需要 JDBC 驱动 只需要 JDBC 驱动
功能 功能最全(表空间、用户、全库、并行等) 最灵活,可自定义输出格式 通用性强,便于与其他系统集成
易用性 配置稍复杂(需创建目录) 代码相对简单 代码相对简单
适用场景 生产环境数据库备份、迁移 需要可读性强的 SQL 脚本、小数据量备份 数据导出供 Excel 分析、导入其他非 Oracle 数据库

最终建议:

java oracle数据备份-图3
(图片来源网络,侵删)
  • 生产环境备份或大数据量备份必须使用方法一 (expdp),这是 Oracle 官方标准,稳定、高效、可靠。
  • 开发/测试环境的小数据量备份需要特定格式(如 CSV):可以使用方法二或方法三,它们更灵活,不依赖客户端工具。
分享:
扫描分享到社交APP
上一篇
下一篇