Java项目中调用bat批处理配合使用BCP进行多用户数据的备份

一、项目需求

最近项目中需要对数据库(Sql Server系列数据库)进行备份。项目中的需求不是简单的整个数据库的备份,而是根据用户来备份,具体的备份策略如下:

①系统为某一赛事管理类型的系统,整个系统分为几部分,前半部分的处理是在服务器上处理,后半部分的处理,是在用户自己的客户端中处理。不同的赛事对应不同的用户,用户将需要的数据提交给系统进行处理。

②系统在处理完数据之后,用户可以导出自己赛事相关的数据了,这个导出实际上做的就是个备份数据库,只不过只备份属于自己的数据库。然后下载系统的处理结果,很显然,各个用户的数据是不相同的。

③用户下载自己备份的数据库文件(这些个文件会打包成zip压缩包),再导入自己的客户端系统中。

整个系统使用的关键是:程序中调用bcp命令的时候传递用户相关的参数过去就OK了。处理流程如图1-1所示:


Java项目中调用bat批处理进行多用户数据库备份_第1张图片

图1-1 处理流程


二、解决方案

本文提出的是使用Runtime.getRuntime().exec()调用批处理文件,程序中传递相关参数给bcp命令。关于BCP命令的详解,微软的MSDN上面写的非常的详细,在此就不赘述了!下面的一段话引用自微软的msdn上面的一段介绍:


bcp 实用工具可以在 Microsoft SQL Server 实例和用户指定格式的数据文件间大容量复制数据。使用 bcp 实用工具可以将大量新行导入 SQL Server 表,或将表数据导出到数据文件。除非与 queryout 选项一起使用,否则使用该实用工具不需要了解 Transact-SQL 知识。若要将数据导入表中,必须使用为该表创建的格式文件,或者必须了解表的结构以及对于该表中的列有效的数据类型。


参见bcp实用工具.


三、系统的实现

实现主要分为批处理文件的编写和Java程序中的调用两部分,这两部分完成之后,第一部分中的后续问题就好解决了。

①批处理编写:

编写的批处理文件如下,也就是使用bcp工具来进行备份

这里只列出部分表,代码都是一样的,使用的时候稍微修改下即可!

   
   
   
   
  1. @Title 根据userId导出的表  

  2. @bcp "SELECT * FROM sportSys.dbo.competitions where sportSys.dbo.competitions.userId="+%1 queryout %2competitions.xls -T -c >>%3log.txt  

  3. @echo competitions表备份已完成!>>%3log.txt  

  4. @bcp "SELECT * FROM sportSys.dbo.item where sportSys.dbo.item.userId="+%1 queryout %2item.xls -T -c >>%3log.txt  

  5. @echo item表备份已完成!>>%3log.txt  

  6. @bcp "SELECT * FROM sportSys.dbo.itemType where sportSys.dbo.itemType.userId="+%1 queryout %2itemType.xls -T -c >>%3log.txt  

  7. @echo itemType表备份已完成!>>%3log.txt  

  8. @bcp "SELECT * FROM sportSys.dbo.agenda where sportSys.dbo.agenda.userId="+%1 queryout %2agenda.xls -T -c >>%3log.txt  

  9. @echo agenda表备份已完成!>>%3log.txt  

  10. @bcp "SELECT * FROM sportSys.dbo.allroundInfo where sportSys.dbo.allroundInfo.userId="+%1 queryout %2allroundInfo.xls -T -c >>%3log.txt  

  11. @echo allroundInfo表备份已完成!>>%3log.txt  

  12. @bcp "SELECT * FROM sportSys.dbo.athlete where sportSys.dbo.athlete.userId="+%1 queryout %2athlete.xls -T -c >>%3log.txt  

  13. @echo athlete表备份已完成!>>%3log.txt  

  14. @exit

下面,我们来分析下这个批处理


   
   
   
   
  1. bcp "SELECT * FROM sportSys.dbo.competitions where sportSys.dbo.competitions.userId="+%1 queryout %2competitions.xls -T -c >>%3log.txt

bcp 这个是语法

双引号里面写的是Sql语句

%1、%2、%3是要传递给bat文件的参数

%1表示第一个参数,%2表示第二个参数,%3表示第三个参数。

本人的这三个参数的意思分别是:

第一个参数:查询条件

第二个参数:导出文件保存的位置

第三个参数:输出日志保存的位置

②Java程序中调用

导出文件

   
   
   
   
  1. public String backUp() throws Exception {  

  2. try {  

  3.            HttpServletRequest request = ServletActionContext.getRequest();  

  4.            HttpServletResponse response = ServletActionContext.getResponse();  

  5.            Map session = ActionContext.getContext().getSession();  

  6.            String userId = session.get("userId").toString();  

  7.            String homeDir1 = request.getSession().getServletContext()  

  8.            .getRealPath("/");  

  9. //Runtime.getRuntime().exec("notepad");

  10.            Runtime.getRuntime().exec(  

  11. "cmd /k start /b "+homeDir1+"backUp\\backupBy.bat" + " " + userId + " " +  

  12.                    homeDir1+"download"+File.separatorChar + userId+File.separatorChar+ " " +  

  13.                    homeDir1+"download"+File.separatorChar + userId+File.separatorChar);  

  14.        } catch (IOException e) {  

  15. // TODO Auto-generated catch block

  16.            e.printStackTrace();  

  17. return INPUT;  

  18.        }  

  19. return SUCCESS;  

  20.    }

Java中调用命令行程序,的方式就是第11行

   
   
   
   
  1. Runtime.getRuntime().exec(  

  2. "cmd /k start /b "+homeDir1+"backUp\\backupBy.bat" + " " + userId + " " +  

  3.                    homeDir1+"download"+File.separatorChar + userId+File.separatorChar+ " " +  

  4.                    homeDir1+"download"+File.separatorChar + userId+File.separatorChar);


Runtime:

每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime方法获取当前运行时。

应用程序不能创建自己的 Runtime 类实例。

   
   
   
   
  1. public Process exec(String command)  

  2.             throws IOException在单独的进程中执行指定的字符串命令。  

  3. 这是一个很有用的方法。对于 exec(command) 形式的调用而言,其行为与调用 exec(command, null, null) 完全相同。  

  4. 参数:  

  5. command - 一条指定的系统命令。  

  6. 返回:  

  7. 一个新的 Process 对象,用于管理子进程  

  8. 抛出:  

  9. SecurityException - 如果安全管理器存在,并且其 checkExec 方法不允许创建子进程  

  10. IOException - 如果发生 I/O 错误  

  11. NullPointerException - 如果 command 为 null  

  12. IllegalArgumentException - 如果 command 为空  

  13. 另请参见:  

  14. exec(String[], String[], File), ProcessBuilder  

为了使调用bat文件来运行的时候,不至于弹出一个黑框,

加入/b参数 即可解决!

③导入


   
   
   
   
  1. bcp sportSys.dbo.competitions in %1competitions.xls -c -t

以上便是导入的指令! 同样也可以设置将待导入的文件全部放在目录中,然后导入即可!

④压缩成Zip文件-工具类(网上有写好的ZipUtil)


   
   
   
   
  1. package com.yaxing.util;  

  2. import java.io.BufferedInputStream;  

  3. import java.io.File;  

  4. import java.io.FileInputStream;  

  5. import java.io.FileNotFoundException;  

  6. import java.io.FileOutputStream;  

  7. import java.io.IOException;  

  8. import java.util.jar.JarEntry;  

  9. import java.util.jar.JarOutputStream;  

  10. import java.util.jar.Manifest;  

  11. publicclass ZipUtil {  

  12. protectedstaticbyte[] buf = newbyte[1024];  

  13. /**

  14.     * 私有构造函数防止被构建

  15.     */

  16. private ZipUtil() {  

  17.    }  

  18. /**

  19.     * 遍历目录并添加文件.

  20.     *  

  21.     * @param jos

  22.     *            - JAR 输出流

  23.     * @param file

  24.     *            - 目录文件名

  25.     * @param pathName

  26.     *            - ZIP中的目录名

  27.     * @throws IOException

  28.     * @throws FileNotFoundException

  29.     */

  30. privatestaticvoid recurseFiles(final JarOutputStream jos,  

  31. final File file, final String pathName) throws IOException,  

  32.            FileNotFoundException {  

  33. // 文件夹则往下遍历

  34. if (file.isDirectory()) {  

  35. final String sPathName = pathName + file.getName() + "/";  

  36.            jos.putNextEntry(new JarEntry(sPathName));  

  37. final String[] fileNames = file.list();  

  38. if (fileNames != null) {  

  39. for (int i = 0; i < fileNames.length; i++) {  

  40.                    recurseFiles(jos, new File(file, fileNames[i]), sPathName);  

  41.                }  

  42.            }  

  43.        }  

  44. // 读取文件到ZIP/JAR文件条目

  45. else {  

  46. // 使用指定名称创建新的 ZIP/JAR 条目

  47. final JarEntry jarEntry = new JarEntry(pathName + file.getName());  

  48. final FileInputStream fin = new FileInputStream(file);  

  49. final BufferedInputStream in = new BufferedInputStream(fin);  

  50. // 开始写入新的 ZIP 文件条目并将流定位到条目数据的开始处。

  51.            jos.putNextEntry(jarEntry);  

  52. int len;  

  53. while ((len = in.read(buf)) >= 0) {  

  54. // 将字节数组写入当前 ZIP 条目数据

  55.                jos.write(buf, 0, len);  

  56.            }  

  57.            in.close();  

  58. // 关闭当前 ZIP 条目并定位流以写入下一个条目

  59.            jos.closeEntry();  

  60.        }  

  61.    }  

  62. /**

  63.     * 创建 ZIP/JAR 文件.

  64.     *  

  65.     * @param directory

  66.     *            - 要添加的目录

  67.     * @param zipFile

  68.     *            - 保存的 ZIP 文件名

  69.     * @param zipFolderName

  70.     *            - ZIP 中的路径名

  71.     * @param level

  72.     *            - 压缩级别(0~9)

  73.     * @throws IOException

  74.     * @throws FileNotFoundException

  75.     */

  76. publicstaticvoid makeDirectoryToZip(final File directory,  

  77. final File zipFile, final String zipFolderName, finalint level)  

  78. throws IOException, FileNotFoundException {  

  79.        FileOutputStream fos = null;  

  80. try {  

  81. // 输出文件流

  82.            fos = new FileOutputStream(zipFile);  

  83.        } catch (final Exception e) {  

  84. // 建立打包后的空文件

  85. new File(zipFile.getParent()).mkdirs();  

  86.            zipFile.createNewFile();  

  87.            fos = new FileOutputStream(zipFile);  

  88.        }  

  89. // 使用指定的 Manifest 创建新的 JarOutputStream。清单作为输出流的第一个条目被写入

  90. final JarOutputStream jos = new JarOutputStream(fos, new Manifest());  

  91.        jos.setLevel(checkZipLevel(level));  

  92. final String[] fileNames = directory.list();  

  93. if (fileNames != null) {  

  94. for (int i = 0; i < fileNames.length; i++) {  

  95. // 对一级目录下的所有文件或文件夹进行处理

  96.                recurseFiles(jos, new File(directory, fileNames[i]),  

  97.                        zipFolderName == null ? "" : zipFolderName);  

  98.            }  

  99.        }  

  100. // 关闭 ZIP 输出流和正在过滤的流。

  101.        jos.close();  

  102.    }  

  103. /**

  104.     * 检查并设置有效的压缩级别,避免压缩级别设置错的异常

  105.     *  

  106.     * @param level

  107.     *            - 压缩级别

  108.     * @return 有效的压缩级别或者默认压缩级别

  109.     */

  110. publicstaticint checkZipLevel(finalint level) {  

  111. if (level < 0 || level > 9) {  

  112. return7;  

  113.        } else {  

  114. return level;  

  115.        }  

  116.    }  

  117. publicstaticvoid main(final String args[]) throws FileNotFoundException,  

  118.            IOException {  

  119. // makeDirectoryToZip();

  120. final String homeDir = System.getProperty("user.dir");  

  121.        System.out.println(homeDir);  

  122. final File zipFile = new File(homeDir, "download" + File.separatorChar  

  123.                + "test.zip");  

  124. final File pagesDirectory = new File(homeDir, "src");  

  125.        System.out.println("Making zip file from folder /src to " + zipFile);  

  126.        ZipUtil.makeDirectoryToZip(pagesDirectory, zipFile, "", 9);  

  127.        System.out.println("Zip file " + zipFile + " has been made.");  

  128.    }  

  129. }  

⑤Struts2执行Action里面的方法


   
   
   
   
  1. public String execute() throws Exception {  

  2.    HttpServletRequest request = ServletActionContext.getRequest();  

  3.    HttpServletResponse response = ServletActionContext.getResponse();  

  4.    Map session = ActionContext.getContext().getSession();  

  5.    String userId = session.get("userId").toString();  

  6. final String homeDir = request.getSession().getServletContext()  

  7.            .getRealPath("/");// 打包后文件的位置及名称

  8. final File zipFile = new File(homeDir, "download" + File.separatorChar  

  9.            + userId + File.separatorChar + userId + "bak.zip");// 要打包的文件夹路径

  10. // if((new File(userId).isDirectory())){

  11. // System.out.println("文件夹"+userId+"已存在!创建失败!");

  12. // }else{

  13. // new File(userId).mkdir();

  14. // System.out.println("创建文件夹"+userId+"成功!");

  15. // }

  16. final File pagesDirectory = new File(homeDir, "download/" + userId);  

  17.    LOG.info("Making zip file from folder /" + userId + " to " + zipFile);// 压缩文件

  18.    ZipUtil.makeDirectoryToZip(pagesDirectory, zipFile, "", 9);  

  19.    LOG.info("Zip file " + zipFile + " has been made.");  

  20.    response.sendRedirect("../download/" + userId + "/" + userId  

  21.            + "bak.zip");  

  22. returnnull;  

  23. // return super.execute();

  24. }

压缩文件创建之后,返回该压缩包,提供用户下载!用户将下载的文件导入客户端系统中,即可,


四、总结

本文总结了Java中调用批处理文件,给批处理文件传递参数的使用方法。依据此,可以实现项目中多种用途的数据备份。

  欢迎各位交流,如有更好的方法,欢迎指出,谢谢!