Java项目中调用bat批处理配合使用BCP进行多用户数据的备份
一、项目需求
最近项目中需要对数据库(Sql Server系列数据库)进行备份。项目中的需求不是简单的整个数据库的备份,而是根据用户来备份,具体的备份策略如下:
①系统为某一赛事管理类型的系统,整个系统分为几部分,前半部分的处理是在服务器上处理,后半部分的处理,是在用户自己的客户端中处理。不同的赛事对应不同的用户,用户将需要的数据提交给系统进行处理。
②系统在处理完数据之后,用户可以导出自己赛事相关的数据了,这个导出实际上做的就是个备份数据库,只不过只备份属于自己的数据库。然后下载系统的处理结果,很显然,各个用户的数据是不相同的。
③用户下载自己备份的数据库文件(这些个文件会打包成zip压缩包),再导入自己的客户端系统中。
整个系统使用的关键是:程序中调用bcp命令的时候传递用户相关的参数过去就OK了。处理流程如图1-1所示:
图1-1 处理流程
二、解决方案
本文提出的是使用Runtime.getRuntime().exec()调用批处理文件,程序中传递相关参数给bcp命令。关于BCP命令的详解,微软的MSDN上面写的非常的详细,在此就不赘述了!下面的一段话引用自微软的msdn上面的一段介绍:
bcp 实用工具可以在 Microsoft SQL Server 实例和用户指定格式的数据文件间大容量复制数据。使用 bcp 实用工具可以将大量新行导入 SQL Server 表,或将表数据导出到数据文件。除非与 queryout 选项一起使用,否则使用该实用工具不需要了解 Transact-SQL 知识。若要将数据导入表中,必须使用为该表创建的格式文件,或者必须了解表的结构以及对于该表中的列有效的数据类型。
三、系统的实现
实现主要分为批处理文件的编写和Java程序中的调用两部分,这两部分完成之后,第一部分中的后续问题就好解决了。
①批处理编写:
编写的批处理文件如下,也就是使用bcp工具来进行备份
这里只列出部分表,代码都是一样的,使用的时候稍微修改下即可!@Title 根据userId导出的表
@bcp "SELECT * FROM sportSys.dbo.competitions wheresportSys.dbo.competitions.userId="+%1 queryout %2competitions.xls -T -c>>%3log.txt
@echo competitions表备份已完成!>>%3log.txt
@bcp "SELECT * FROM sportSys.dbo.item wheresportSys.dbo.item.userId="+%1 queryout %2item.xls -T -c>>%3log.txt
@echo item表备份已完成!>>%3log.txt
@bcp "SELECT * FROM sportSys.dbo.itemType wheresportSys.dbo.itemType.userId="+%1 queryout %2itemType.xls -T -c>>%3log.txt
@echo itemType表备份已完成!>>%3log.txt
@bcp "SELECT * FROM sportSys.dbo.agenda wheresportSys.dbo.agenda.userId="+%1 queryout %2agenda.xls -T -c>>%3log.txt
@echo agenda表备份已完成!>>%3log.txt
@bcp "SELECT * FROM sportSys.dbo.allroundInfo wheresportSys.dbo.allroundInfo.userId="+%1 queryout %2allroundInfo.xls -T -c>>%3log.txt
@echo allroundInfo表备份已完成!>>%3log.txt
@bcp "SELECT * FROM sportSys.dbo.athlete wheresportSys.dbo.athlete.userId="+%1 queryout %2athlete.xls -T -c>>%3log.txt
@echo athlete表备份已完成!>>%3log.txt
@exit
下面,我们来分析下这个批处理
bcp "SELECT * FROM sportSys.dbo.competitions wheresportSys.dbo.competitions.userId="+%1 queryout %2competitions.xls -T -c>>%3log.txt
bcp 这个是语法
双引号里面写的是Sql语句
%1、%2、%3是要传递给bat文件的参数
%1表示第一个参数,%2表示第二个参数,%3表示第三个参数。
本人的这三个参数的意思分别是:
第一个参数:查询条件
第二个参数:导出文件保存的位置
第三个参数:输出日志保存的位置
②Java程序中调用
导出文件publicString backUp()throwsException {
try{
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
Map session = ActionContext.getContext().getSession();
String userId = session.get("userId").toString();
String homeDir1 = request.getSession().getServletContext()
.getRealPath("/");
//Runtime.getRuntime().exec("notepad");
Runtime.getRuntime().exec(
"cmd /k start /b "+homeDir1+"backUp\\backupBy.bat"+" "+ userId +" "+
homeDir1+"download"+File.separatorChar + userId+File.separatorChar+" "+
homeDir1+"download"+File.separatorChar + userId+File.separatorChar);
}catch(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
returnINPUT;
}
returnSUCCESS;
}
Java中调用命令行程序,的方式就是第11行Runtime.getRuntime().exec(
"cmd /k start /b "+homeDir1+"backUp\\backupBy.bat"+" "+ userId +" "+
homeDir1+"download"+File.separatorChar + userId+File.separatorChar+" "+
homeDir1+"download"+File.separatorChar + userId+File.separatorChar);
Runtime:
每个 Java 应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时。
应用程序不能创建自己的 Runtime 类实例。public Process exec(String command)
throws IOException在单独的进程中执行指定的字符串命令。
这是一个很有用的方法。对于 exec(command) 形式的调用而言,其行为与调用 exec(command, null, null) 完全相同。
参数:
command - 一条指定的系统命令。
返回:
一个新的 Process 对象,用于管理子进程
抛出:
SecurityException - 如果安全管理器存在,并且其 checkExec 方法不允许创建子进程
IOException - 如果发生 I/O 错误
NullPointerException - 如果 command 为 null
IllegalArgumentException - 如果 command 为空
另请参见:
exec(String[], String[], File), ProcessBuilder
为了使调用bat文件来运行的时候,不至于弹出一个黑框,
加入/b参数 即可解决!
③导入
bcp sportSys.dbo.competitions in %1competitions.xls -c -t
以上便是导入的指令! 同样也可以设置将待导入的文件全部放在目录中,然后导入即可!
④压缩成Zip文件-工具类(网上有写好的ZipUtil)
packagecom.yaxing.util;
importjava.io.BufferedInputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileNotFoundException;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.util.jar.JarEntry;
importjava.util.jar.JarOutputStream;
importjava.util.jar.Manifest;
publicclassZipUtil {
protectedstaticbyte[] buf =newbyte[1024];
/**
* 私有构造函数防止被构建
*/
privateZipUtil() {
}
/**
* 遍历目录并添加文件.
*
* @param jos
* - JAR 输出流
* @param file
* - 目录文件名
* @param pathName
* - ZIP中的目录名
* @throws IOException
* @throws FileNotFoundException
*/
privatestaticvoidrecurseFiles(finalJarOutputStream jos,
finalFile file,finalString pathName)throwsIOException,
FileNotFoundException {
// 文件夹则往下遍历
if(file.isDirectory()) {
finalString sPathName = pathName + file.getName() +"/";
jos.putNextEntry(newJarEntry(sPathName));
finalString[] fileNames = file.list();
if(fileNames !=null) {
for(inti =0; i < fileNames.length; i++) {
recurseFiles(jos,newFile(file, fileNames[i]), sPathName);
}
}
}
// 读取文件到ZIP/JAR文件条目
else{
// 使用指定名称创建新的 ZIP/JAR 条目
finalJarEntry jarEntry =newJarEntry(pathName + file.getName());
finalFileInputStream fin =newFileInputStream(file);
finalBufferedInputStream in =newBufferedInputStream(fin);
// 开始写入新的 ZIP 文件条目并将流定位到条目数据的开始处。
jos.putNextEntry(jarEntry);
intlen;
while((len = in.read(buf)) >=0) {
// 将字节数组写入当前 ZIP 条目数据
jos.write(buf,0, len);
}
in.close();
// 关闭当前 ZIP 条目并定位流以写入下一个条目
jos.closeEntry();
}
}
/**
* 创建 ZIP/JAR 文件.
*
* @param directory
* - 要添加的目录
* @param zipFile
* - 保存的 ZIP 文件名
* @param zipFolderName
* - ZIP 中的路径名
* @param level
* - 压缩级别(0~9)
* @throws IOException
* @throws FileNotFoundException
*/
publicstaticvoidmakeDirectoryToZip(finalFile directory,
finalFile zipFile,finalString zipFolderName,finalintlevel)
throwsIOException, FileNotFoundException {
FileOutputStream fos =null;
try{
// 输出文件流
fos =newFileOutputStream(zipFile);
}catch(finalException e) {
// 建立打包后的空文件
newFile(zipFile.getParent()).mkdirs();
zipFile.createNewFile();
fos =newFileOutputStream(zipFile);
}
// 使用指定的 Manifest 创建新的 JarOutputStream。清单作为输出流的第一个条目被写入
finalJarOutputStream jos =newJarOutputStream(fos,newManifest());
jos.setLevel(checkZipLevel(level));
finalString[] fileNames = directory.list();
if(fileNames !=null) {
for(inti =0; i < fileNames.length; i++) {
// 对一级目录下的所有文件或文件夹进行处理
recurseFiles(jos,newFile(directory, fileNames[i]),
zipFolderName ==null?"": zipFolderName);
}
}
// 关闭 ZIP 输出流和正在过滤的流。
jos.close();
}
/**
* 检查并设置有效的压缩级别,避免压缩级别设置错的异常
*
* @param level
* - 压缩级别
* @return 有效的压缩级别或者默认压缩级别
*/
publicstaticintcheckZipLevel(finalintlevel) {
if(level <0|| level >9) {
return7;
}else{
returnlevel;
}
}
publicstaticvoidmain(finalString args[])throwsFileNotFoundException,
IOException {
// makeDirectoryToZip();
finalString homeDir = System.getProperty("user.dir");
System.out.println(homeDir);
finalFile zipFile =newFile(homeDir,"download"+ File.separatorChar
+"test.zip");
finalFile pagesDirectory =newFile(homeDir,"src");
System.out.println("Making zip file from folder /src to "+ zipFile);
ZipUtil.makeDirectoryToZip(pagesDirectory, zipFile,"",9);
System.out.println("Zip file "+ zipFile +" has been made.");
}
}
⑤Struts2执行Action里面的方法
publicString execute()throwsException {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
Map session = ActionContext.getContext().getSession();
String userId = session.get("userId").toString();
finalString homeDir = request.getSession().getServletContext()
.getRealPath("/");// 打包后文件的位置及名称
finalFile zipFile =newFile(homeDir,"download"+ File.separatorChar
+ userId + File.separatorChar + userId +"bak.zip");// 要打包的文件夹路径
// if((new File(userId).isDirectory())){
// System.out.println("文件夹"+userId+"已存在!创建失败!");
// }else{
// new File(userId).mkdir();
// System.out.println("创建文件夹"+userId+"成功!");
// }
finalFile pagesDirectory =newFile(homeDir,"download/"+ userId);
LOG.info("Making zip file from folder /"+ userId +" to "+ zipFile);// 压缩文件
ZipUtil.makeDirectoryToZip(pagesDirectory, zipFile,"",9);
LOG.info("Zip file "+ zipFile +" has been made.");
response.sendRedirect("../download/"+ userId +"/"+ userId
+"bak.zip");
returnnull;
// return super.execute();
}
压缩文件创建之后,返回该压缩包,提供用户下载!用户将下载的文件导入客户端系统中,即可,
四、总结
本文总结了Java中调用批处理文件,给批处理文件传递参数的使用方法。依据此,可以实现项目中多种用途的数据备份。
欢迎各位交流,如有更好的方法,欢迎指出,谢谢!