近期开发的项目是一个基金管理的项目,由于交易数据比较大,为了提高响应速度,在数据访问层增加了缓存机制。但是,随着系统运行了一段时间,由于缓存加载的数据量扩大,系统内存占用越来越高。为了减轻系统压力,需要增加归档功能,即通过归档操作,将数据库里面符合特定条件(如:交易完成)的数据进行清理。但是,由于基金交易数据的特殊性,这些被清理的数据仍需要保存,而且必要的情况下还要支持数据的恢复。这篇文章就是这个功能开发完成的产物。
最开始,由于dump文件格式的不确定,加上要用Java代码生成,我们考虑生成dump文件的内容为需要归档的数据的insert语句。首先,我们通过需要归档的表名,查询user_tab_columns可以拿到该表的所有列信息(主要是列名和数据类型),然后根据表名和列信息拼成insert语句,最后加上归档条件。根据该思路,我们有了下面的代码。
package com.code.dump;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
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 DumpFile {
/** 数据库连接参数 */
static final String DB_HOST = "127.0.0.1:1526";
static final String DB_SID = "orcl";
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";
static Connection connection = null;
public static void main(String[] args) {
try {
long start = System.currentTimeMillis();
String sql = generateInsertSql();
generateDumpFileByFile(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();
}
/** 通过io操作生成dump文件 */
private static void generateDumpFileByFile(String sql) throws Exception {
// 创建文件存储目录
File directory = new File(DUMP_FILE_PATH);
if (!directory.exists()) {
directory.mkdirs();
}
// 生成的dump文件
String dumpFilePath = DUMP_FILE_PATH + "/" + TABLE_NAME + "_dump_" + getTimeStamp() + ".dump";
if (!Files.exists(Paths.get(dumpFilePath))) {
Files.createFile(Paths.get(dumpFilePath));
}
FileChannel dumpFileChannel = FileChannel.open(Paths.get(dumpFilePath), StandardOpenOption.READ, StandardOpenOption.WRITE);
List<String> insertSqls = new ArrayList<String>();
ResultSet results = connection.createStatement().executeQuery(sql);
while (results.next()) {
String insertSql = results.getString(1);
insertSqls.add(insertSql);
}
System.out.println("---------------------------\n"+"rows count is :"+insertSqls.size()+"\n-----------------------------\n");
for (String insertSql : insertSqls) {
dumpFileChannel.write(ByteBuffer.wrap((insertSql + "\n").getBytes()));
}
dumpFileChannel.close();
}
static String getTimeStamp() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
return sdf.format(new Date());
}
}
class ColumnInfo {
private String columnName;
private String dataType;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public ColumnInfo(String columnName, String dataType) {
super();
this.columnName = columnName;
this.dataType = dataType;
}
}
程序的输出结果:
---------------------------
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
-----------------------------
---------------------------
rows count is :23099
-----------------------------
---------------------------
all cost time is 63195 ms
-----------------------------
dump文件的内容截取了一部分:
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('CSMS',12275,12231,'AH3811(Asset Unit)-AH3811',6,128,'AH3811',12275,'N',2785513347,1,42,'lzsysadmin','2016051807044700','xuxj194','2010102801403483','lzsysadmin','2016051807044700','分红交易-投管-PLR-场外(资产单元)-AH3811','','');
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('CSMS',12276,12231,'AH3871(Asset Unit)-AH3871',6,128,'AH3871',12276,'N',2785513353,1,42,'lzsysadmin','2016051807045600','xuxj194','2010102801403491','lzsysadmin','2016051807045600','分红交易-投管-ALR-场外(资产单元)-AH3871','','');
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('CSMS',12185,12122,'034708LAFS(Asset Unit)-AG2G41',6,128,'AG2G41',12185,'N',null,null,null,'','','xuxj194','2010102801393155','fundPostn','2016051806431500','分红-存管-LAFS-场内-深A-0899034705(资产单元)-AG2G41','','');
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('CSMS',12186,12122,'B49944LAFS(Asset Unit)-AG2G42',6,128,'AG2G42',12186,'N',null,null,null,'','','xuxj194','2010102801393160','fundPostn','2016051806425500','分红-存管-LAFS-场内-沪A-B880349945(资产单元)-AG2G42','','');
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('CSMS',12187,12122,'124224LAFS(Asset Unit)-AG2H51',6,128,'AG2H51',12187,'N',null,null,null,'','','xuxj194','2010102801393164','fundPostn','2016051806444000','分红-存管(基金)-LAFS-场内-深A-0019124224(资产单元)-AG2H51','','');
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('CSMS',12188,12122,'03470eLAFS(Asset Unit)-AG2I61',6,128,'AG2I61',12188,'N',null,null,null,'','','xuxj194','2010102801393169','fundPostn','2016051806445500','分红-存管-LAFS-场内-深A-0899034705(资产单元)-AG2I61','','');
程序的思路很简单:先调用generateInsertSql()生成select ‘insert into ...‘,再调用generateDumpFileByFile(sql)将上面生成的sql语句查询出来的结果存到dump文件。
该方法完全依赖Java代码,逻辑和执行情况(如异常回滚操作)可控。但是,由于需要通过io写dump文件,效率比较慢,通过上面的输出我们看到,23099条数据总共花费了63秒多。另外,该方法由于需要拼接 insert into 语句,不支持CLOB、BLOB等特殊类型属性。
为了提高dump文件的生成效率,我们有了Java项目生成Oracle数据库dump文件(二)的改造。