Runtime.exec()备份MySQL数据库出现的问题
前几天在用Runtime搞数据库备份,其实也就是使用Runtime.exec()调用mysqldump的指令来备份数据库,由于没怎么接触过Runtime这个类,遇到了一些问题,头发差点都给我撸秃了,先是waitFor(),然后是备份数据库,文件出来了,但是是空文件。
关于waitFor()堵塞
/*Win下,/C指执行字符串指令然后终止,属于cmd的属性,在cmd.exe中,输入cmd/?可以查看*/
String[] command = {"cmd","/C","某个cmd指令"};
/*Linux下*/
String[] command1 = {"/bin/sh","-c","某个linux指令"};
/*三个参数,cmd指令、环境变量、启动在哪个目录(不指定则在当前目录启动cmd.exe)*/
Process p = Runtime.getRuntime.exec(command,null,null);
p.waitFor();
上面的command是指创建子进程,执行start后面的cmd指令,并且打开cmd.exe。
上面这个就是创建子进程并执行某个命令
waitFor()堵塞是因为,子进程创建时,会伴随着输入、输出、错误三种流的出现,输出倒是没什么,但是如果你的指令会有大量输入,即cmd中的输出,相对于java程序来说是输入,那你输入缓冲区读了一堆乱七八糟的东西,没人来给它排,那自然就堵在那儿,出也没地方出,进也没地方进了。
所以就需要创建地方,来专门容纳它们
String line = null;
StringBuilder inmessage = new StringBuilder();
InputStreamReader in = null;
BufferedReader br = null;
//创建输入流,以防止输入流缓冲区满了后导致process堵塞
try {
/*用process.getInputStream()获取输入流,返回值是InputStream类型*/
in = new InputStreamReader(process.getInputStream());
br = new BufferedReader(in);
/*读出*/
while((line = br.readLine()) != null) {
inmessage.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally{/*关闭流*/
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
错误流和它输入流差不多,代码都是一样的,只不过获取是
process.getErrorStream();
关于备份数据库为空的问题
因为对于Runtime这个类不熟悉,所以我在这个问题上纠结了很久很久,上网查了一下大家的出现问题的地方,有些因为空格问题,有些因为这个程序的运行环境问题等等
首先我的大概代码(简化)就是
String[] command = {"cmd","/C","start mysqldump -uroot -p123456 kdwl > D:\\kdwl.sql"};
Process p = Runtime.getRuntime.exec(command,null,null);
p.waitFor();
问题1
后面推测可能是由于start命令导致的备份出的数据为0KB,具体原由未知
解决方案
把指令写进.bat批处理文件,然后再执行批处理文件即可
大致代码就是
String[] command = {"mysqldump.bat"};
Process p = Runtime.getRuntime.exec(command,null,null);
p.waitFor();
这样就可以成功备份,并且不会有空的备份文件了
或者
/*删除start*/
String[] command = {"cmd","/C","mysqldump -uroot -p123456 kdwl > D:\\kdwl.sql"};
Process p = Runtime.getRuntime.exec(command,null,null);
p.waitFor();
或者
/*为start加上/B属性,/B启动应用程序,但不创建新窗口*/
String[] command = {"cmd","/C","start /B mysqldump -uroot -p123456 kdwl > D:\\kdwl.sql"};
Process p = Runtime.getRuntime.exec(command,null,null);
p.waitFor();
我的疑惑
我的waitFor()是不堵塞的,并且显示成功运行,确实也在这个D盘创建了个kdwl.sql的文件,但是就是空的,为此我还去查了MySQL日志,与正常备份的作比较,不过就是摸不着头脑,因为对于MySQL来说,这个并没有出现在错误日志里,而是正常出现在了日志里。
2020-04-02T08:16:40.572694Z 67 Query SET SESSION character_set_results = 'utf8mb4'
2020-04-02T08:16:40.577407Z 67 Quit
上面没贴出来,上面与正常成功的备份没有什么不同,就像是运行到这步突然断掉了联系
然后我将start删去之后,发现了指令可以成功备份了,并且不是空文件,也就是
String[] command = {"cmd","/C","mysqldump -uroot -p123456 kdwl > D:\\kdwl.sql"};
Process p = Runtime.getRuntime.exec(command,null,null);
p.waitFor();
然后我去查了一下start指令的解释
启动一个单独的窗口以运行指定的程序或命令。
这是cmd中给出的解释
关于这个指令start 备份数据库,在cmd.exe中运行也是输出一个空sql文件
我的猜测
这个start是开启一个单独的窗口运行指定的程序或命令,但是mysqldump.exe本身运行后就会闪退,我们一般都是在cmd或是其他工具中备份的,所以导致了命令本身没有被执行完,然后mysqldump.exe一闪而过,备份也没备份的完,就导致了0KB的情况,我也试过了,如果在执行备份后,快速关闭cmd.exe也确实会有1KB等现象出现。
问题2
我搜索出来的很多人是因为路径问题
解决方案
一般来说不会有这个问题,因为大家在Windows上用MySQL的时候都会在环境变量里配置bin的路径,然后运行mysqldump时,会自动去找。
检查你的mysqldump.exe的路径是否有空格,或者是否正确
总结
1. waitFor()阻塞问题
加入两个输入流,用来输入你的指令带出的一堆输出物
2. 备份的数据库为空问题,
使用批处理文件储存命令,然后调用批处理文件,
如果用了start命令,删去start即可,当然加上/B属性也可以(具体原因未知),
mysqldump.exe路径有问题,或是有空格