Oracle数据库客户端有一个sqlplus组件,这个组件能够让我们通过命令行的方式执行一些数据库操作。为了提高dump文件的写入效率,我们尝试使用sqlplus命令来生成dump文件。
sqlplus可以指定运行脚本,例如:
sqlplus admin/pwd@//127.0.0.1:1526/orcl@./tmp_sql/NODE_dump_sql_20180516130228316.sql
我们在这个脚本里面可以通过spool命令打开一个文件,然后将sql查询出来的结果写入这个文件。
具体代码如下:
package com.code.dump;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class DumpFile2 {
/** 数据库连接参数 */
static final String DB_HOST = "127.0.0.1:1526";
static final String DB_SID = "oracl";
static final String DB_USER = "admin";
static final String DB_PWD = "pwd";
static final String TABLE_NAME = "NODE";
/** 拼接的sql每行字符数,超过ROW_CHAR_COUNT就换行,防止sql过长 */
static final int ROW_CHAR_COUNT = 1000;
/** dump文件保存路径 */
static final String DUMP_FILE_PATH = "./dump_file";
/** sqlPlus执行脚本存放路径 */
static final String SQL_PLUS_FILE_PATH = "./tmp_sql";
static Connection connection = null;
public static void main(String[] args) {
try {
long start = System.currentTimeMillis();
String sql = generateInsertSql();
generateDumpFileBySqlPlus(sql);
System.out.println("---------------------------\n"+"all cost time is " + (System.currentTimeMillis() - start) + " ms"+"\n-----------------------------\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/** 生成使用的sql语句 */
static String generateInsertSql() throws Exception {
// 存储需要备份的表的列的信息(column_name,data_type)
List<ColumnInfo> cols = new ArrayList<ColumnInfo>();
// 创建oracle数据库连接
Class.forName("oracle.jdbc.driver.OracleDriver");
connection = DriverManager.getConnection("jdbc:oracle:thin:@" + DB_HOST + ":" + DB_SID, DB_USER, DB_PWD);
Statement sql = connection.createStatement();
// 查询需备份表的所有列信息
ResultSet columns = sql.executeQuery("select column_name, data_type from user_tab_columns where table_name='" + TABLE_NAME.toUpperCase() + "'");
while (columns.next()) {
ColumnInfo columnInfo = new ColumnInfo(columns.getString(1), columns.getString(2));
cols.add(columnInfo);
}
// 拼接insert的前半部分
// eg:insert into charge_value_lookup(RESULT_ID,TAX_IDS,FEE_IDS) values(
StringBuffer insertSql = new StringBuffer("insert into " + TABLE_NAME + "(");
int index = 1;
for (ColumnInfo col : cols) {
if (insertSql.length() > ROW_CHAR_COUNT * index) {
insertSql.append("\n");
index++;
}
insertSql.append(col.getColumnName()).append(",");
}
insertSql.delete(insertSql.length() - 1, insertSql.length());
insertSql.append(") values(");
System.out.println("---------------------------\n"+insertSql.toString()+"\n-----------------------------\n");
// 拼接sql调用的查询语句
// eg:select 'insert into charge_value_lookup(RESULT_ID,TAX_IDS,FEE_IDS) values(' ||''''||RESULT_ID||''''||','||''''||TAX_IDS||''''||','||''''||FEE_IDS||''''||');' from charge_value_lookup
StringBuffer excSql = new StringBuffer("select '").append(insertSql).append("' ");
index = 1;
for (int i = 0; i < cols.size(); i++) {
if (excSql.length() > ROW_CHAR_COUNT * index) {
excSql.append("\n");
index++;
}
ColumnInfo columnInfo = cols.get(i);
switch (columnInfo.getDataType()) {
case "CHAR":
case "VARCHAR2":
excSql.append("||").append("''''").append("||").append(columnInfo.getColumnName()).append("||").append("''''");
break;
case "NUMBER":
case "FLOAT":
case "LONG":
excSql.append("||").append("nvl(to_char(" + columnInfo.getColumnName() + ")," + columnInfo.getColumnName() + "||'null')");
break;
case "DATE":
break;
default:
throw new Exception("表的列类型不支持");
}
if (i != cols.size() - 1) {
excSql.append("||").append("','");
} else {
excSql.append("||');'");
}
}
excSql.append(" from " + TABLE_NAME + "");
System.out.println("---------------------------\n"+excSql.toString()+"\n-----------------------------\n");
return excSql.toString();
}
/** 通过sqlPlus操作生成dump文件 */
private static void generateDumpFileBySqlPlus(String sql) throws Exception {
// 创建文件存储目录
File directory = new File(DUMP_FILE_PATH);
if (!directory.exists()) {
directory.mkdirs();
}
directory = new File(SQL_PLUS_FILE_PATH);
if (!directory.exists()) {
directory.mkdirs();
}
// sqlPlus调用的sql脚本
String genSqlPlusFilePath = SQL_PLUS_FILE_PATH + "/" + TABLE_NAME + "_dump_sql_" + getTimeStamp() + ".sql";
if (!Files.exists(Paths.get(genSqlPlusFilePath))) {
Files.createFile(Paths.get(genSqlPlusFilePath));
}
// 生成的dump文件
String dumpFilePath = DUMP_FILE_PATH + "/" + TABLE_NAME + "_dump_" + getTimeStamp() + ".dump";
if (!Files.exists(Paths.get(dumpFilePath))) {
Files.createFile(Paths.get(dumpFilePath));
}
// sqlPlus 命令头
final String[] headers = new String[] { "set echo off;", "set feedback off;", "set term off;", "set heading off;", "set sqlblanklines off;",
"set linesize 5000;", "spool " + dumpFilePath + ";" };
final String[] tails = new String[] { "spool off;", "exit;" };
// 创建sqlplus可执行的sql文件
FileChannel fileChannel = FileChannel.open(Paths.get(genSqlPlusFilePath), StandardOpenOption.READ, StandardOpenOption.WRITE);
for (String header : headers) {
fileChannel.write(ByteBuffer.wrap((header + "\n").getBytes()));
}
fileChannel.write(ByteBuffer.wrap((sql + ";\n").getBytes()));
for (String tail : tails) {
fileChannel.write(ByteBuffer.wrap((tail + "\n").getBytes()));
}
fileChannel.close();
String sqlPlusCmd = "sqlplus " + DB_USER + "/" + DB_PWD + "@//" + DB_HOST + "/" + DB_SID + " @" + genSqlPlusFilePath;
System.out.println(sqlPlusCmd);
Process process = Runtime.getRuntime().exec(sqlPlusCmd);
process.waitFor();
InputStream is = process.getInputStream();
printCmdOutMsg(is);
}
static String getTimeStamp() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
return sdf.format(new Date());
}
static void printCmdOutMsg(InputStream is) throws IOException {
System.out.println("----------------start----------------------");
BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("gbk")));
String out = reader.readLine();
while (out != null) {
System.out.println(out);
out = reader.readLine();
}
System.out.println("----------------end----------------------");
}
}
程序的输出结果:
---------------------------
insert into NODE(TENANT_ID,NODE_ID,PARENT_NODE_ID,NAME,NODE_TYPE_ID,HIERARCHY_TYPE_ID,ACCOUNT_ID,SHORTCUT_NODE_ID,SHORTCUT,CHECK_ID,CHECK_STATUS,SCHEDULE_ID,CHECKED_BY,CHECKED_DATE,ADDED_BY,ADDED_DATE,UPDATED_BY,UPDATED_DATE,NAME_1,NAME_2,NAME_3) values(
-----------------------------
---------------------------
select 'insert into NODE(TENANT_ID,NODE_ID,PARENT_NODE_ID,NAME,NODE_TYPE_ID,HIERARCHY_TYPE_ID,ACCOUNT_ID,SHORTCUT_NODE_ID,SHORTCUT,CHECK_ID,CHECK_STATUS,SCHEDULE_ID,CHECKED_BY,CHECKED_DATE,ADDED_BY,ADDED_DATE,UPDATED_BY,UPDATED_DATE,NAME_1,NAME_2,NAME_3) values(' ||''''||TENANT_ID||''''||','||nvl(to_char(NODE_ID),NODE_ID||'null')||','||nvl(to_char(PARENT_NODE_ID),PARENT_NODE_ID||'null')||','||''''||NAME||''''||','||nvl(to_char(NODE_TYPE_ID),NODE_TYPE_ID||'null')||','||nvl(to_char(HIERARCHY_TYPE_ID),HIERARCHY_TYPE_ID||'null')||','||''''||ACCOUNT_ID||''''||','||nvl(to_char(SHORTCUT_NODE_ID),SHORTCUT_NODE_ID||'null')||','||''''||SHORTCUT||''''||','||nvl(to_char(CHECK_ID),CHECK_ID||'null')||','||nvl(to_char(CHECK_STATUS),CHECK_STATUS||'null')||','||nvl(to_char(SCHEDULE_ID),SCHEDULE_ID||'null')||','||''''||CHECKED_BY||''''||','||''''||CHECKED_DATE||''''||','||''''||ADDED_BY||''''||','||''''||ADDED_DATE||''''||','||''''||UPDATED_BY||''''||','||''''||UPDATED_DATE||''''||','||''''||NAME_1||''''||','
||''''||NAME_2||''''||','||''''||NAME_3||''''||');' from NODE
-----------------------------
sqlplus admin/pwd@//127.0.0.1:1526/orcl@./tmp_sql/NODE_dump_sql_20180516130228316.sql
----------------start----------------------
SQL*Plus: Release 11.2.0.1.0 Production on 星期三 5月 16 13:02:32 2018
Copyright (c) 1982, 2010, Oracle. All rights reserved.
连接到:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, Real Application Clusters, Automatic Storage Management, OLAP,
Data Mining and Real Application Testing options
从 Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, Real Application Clusters, Automatic Storage Management, OLAP,
Data Mining and Real Application Testing options 断开
----------------end----------------------
---------------------------
all cost time is 57403 ms
-----------------------------
dump文件内容跟Java项目生成Oracle数据库dump文件(一)里面的dump文件一样。
在这个实现方法里面,我们首先也是拼出里select 'insert into...'语句,然后我们将这个sql语句和一些sqlplus的命令头写入了tmp_sql下面的一个临时文件,这个文件的内容如下:
set echo off;
set feedback off;
set term off;
set heading off;
set sqlblanklines off;
set linesize 5000;
spool ./dump_file/NODE_dump_20180516130228320.dump;
select 'insert into NODE(TENANT_ID,NODE_ID,PARENT_NODE_ID,NAME,NODE_TYPE_ID,HIERARCHY_TYPE_ID,ACCOUNT_ID,SHORTCUT_NODE_ID,SHORTCUT,CHECK_ID,CHECK_STATUS,SCHEDULE_ID,CHECKED_BY,CHECKED_DATE,ADDED_BY,ADDED_DATE,UPDATED_BY,UPDATED_DATE,NAME_1,NAME_2,NAME_3) values(' ||''''||TENANT_ID||''''||','||nvl(to_char(NODE_ID),NODE_ID||'null')||','||nvl(to_char(PARENT_NODE_ID),PARENT_NODE_ID||'null')||','||''''||NAME||''''||','||nvl(to_char(NODE_TYPE_ID),NODE_TYPE_ID||'null')||','||nvl(to_char(HIERARCHY_TYPE_ID),HIERARCHY_TYPE_ID||'null')||','||''''||ACCOUNT_ID||''''||','||nvl(to_char(SHORTCUT_NODE_ID),SHORTCUT_NODE_ID||'null')||','||''''||SHORTCUT||''''||','||nvl(to_char(CHECK_ID),CHECK_ID||'null')||','||nvl(to_char(CHECK_STATUS),CHECK_STATUS||'null')||','||nvl(to_char(SCHEDULE_ID),SCHEDULE_ID||'null')||','||''''||CHECKED_BY||''''||','||''''||CHECKED_DATE||''''||','||''''||ADDED_BY||''''||','||''''||ADDED_DATE||''''||','||''''||UPDATED_BY||''''||','||''''||UPDATED_DATE||''''||','||''''||NAME_1||''''||','
||''''||NAME_2||''''||','||''''||NAME_3||''''||');' from NODE;
spool off;
exit;
最后,我们通过 Runtime.getRuntime().exec方法调用sqlplus命令,启动一个进程去执行这个脚本,得到我们的dump文件。
我们通过输出结果看到,对于同一个表的dump文件生成,这个方法的耗时为57秒,比之前的63秒快了6秒,速度提高的并不明显。
而且,该方法需要在服务器安装sqlplus工具,由于是通过Runtime启动进程来生成dump文件,异常的回滚操作也不太好处理。
后来经过确认,我们需要生成的dump文件其实并不是我们这里自己生成的insert的文本文件,而是Oracle数据库导入/导出数据的一种文件格式。因此,有了下面的方案,Java项目生成oracle数据库dump文件(三)。