java备份还原表数据
背景
需求:这个功能还是费了我一段时间才完成,大体的需求是这样的。 首先是
分模块,每个模块有不同的几个表,备份的时候就按照模块来备份数据,相当于一次性备份多张表的数据了,模块 和 模块拥有的表都是能配置的(即增删改查),主要是选择模块来备份这个麻烦点
思路
- 备份
备份的话我想到的是可以用导出xml文件
的形式,第二种的话只用用mysql原生的mysqldump
命令就好。
最后用的mysqldump命令,这个容易实现一些。 - 还原
还原我查了下有source
命令还原的,还有使用mysqldump -h 主机IP -uroot -p db < /root/db.sql
命令进行还原的
我使用的是后面一种。
命令使用方法
mysqldump命令
1.备份数据库
- 备份所有数据库
mysqldump -h 主机IP -uroot -p --all-database > /usr/dball.sql
- 备份多个数据库
mysqldump -h 主机IP -uroot -p db1 db2 db3 >/usr/db123.sql
- 备份单个数据库
mysqldump -h 主机IP -uroot -p db >/usr/db.sql
2.备份数据库表
- 备份多张表
mysqldump -h 主机IP -uroot -p db table1 table2 >/data/db_table12.sql
- 备份单张表
mysqldump -h 主机IP -uroot -p db table >/data/db_table.sql
- 备份表时用
where
筛选
mysqldump -h 主机IP -uroot -p db table --where " 查询条件" >/data/db_table.sql
还原
- mysqldump命令还原
mysqldump -h 主机IP -uroot -p db < /root/db.sql
- source命令
source /root/db.sql
实现
这里的话前后端是用的若依框架那一套,vue(elmentui)+springboot 实现。
后端
1.备份工具类
先写一个工具类方便调用,因为我前端的想法是把那些数据表的表名都传过来,所以我就留了个表名的参数。改的时候可以根据你传的数据改参数
public class MySQLDatabaseBackupUtils {
/**
* 备份数据库某些表
*
* host IP地址
* userName 数据库的用户名
* password 数据库的密码
* savePath 备份文件的地址
* fileName 备份文件名称
* databaseName 需要备份的数据库的名称
* tableNames 表名如:test1 test2 test3
* mysqlBin mysql安装路径 如:E:\mysql-5.7.24-winx64\bin linux下:/usr/mysql/bin
* 备份成功返回true, 否则返回false
*/
public static boolean backup(String tableNames,String fileName) {
String host = "192.168.16.2";
String userName = "root";
String password = "root";
// String savePath = "D:\\文档\\backupFile";
//服务器的保存地址
String savePath = "/usr/backupFile";
String databaseName = "audit";
///usr/local/mysql/bin 安装路径
String mysqlBin = "/usr/local/mysql/bin";
File saveFile = new File(savePath);
if (!saveFile.exists()) {// 如果目录不存在
saveFile.mkdirs();// 创建文件夹
}
if (!savePath.endsWith(File.separator)) {
savePath = savePath + File.separator;
}
//拼接命令行的命令
// mysqldump --opt --host=localhost --databases backup --tables log_sys sys_user --user=root --password=root --result-file=E:\Sqldata\test.sql --default-character-set=utf8
StringBuilder cmd = new StringBuilder();
cmd.append(mysqlBin).append("/mysqldump").append(" --opt")
.append(" --host=").append(host)
.append(" --databases ").append(databaseName)
.append(" --tables ").append(tableNames)
.append(" --user=").append(userName)
.append(" --password=").append(password)
.append(" --result-file=").append(savePath + fileName)
.append(" --default-character-set=utf8 ");
try {
//调用外部执行exe文件的javaAPI
Process process = Runtime.getRuntime().exec(cmd.toString());
if (process.waitFor() == 0) {// 0 表示线程正常终止
return true;
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
}
2.备份seviceImpl层
我的前端传过来的是 用户选择的模块 以及 模块所属的表名 ,所以我只需要获取表名传入工具类,在用模块名拼接sql文件后缀就ok了。
@Override
public boolean backupModule(List<SysDataBackupTable> sysDataBackupTable) {
List<String> list = sysDataBackupTable.stream().map(SysDataBackupTable::getTableName).collect(Collectors.toList());
String tableNames = String.join(" ", list);
//获取模块名
List<String> collect = sysDataBackupTable.stream().map(SysDataBackupTable::getTableModule).collect(Collectors.toList());
String s = collect.get(0);
String moduleName = s + ".sql";
//调用备份工具类
boolean backup = MySQLDatabaseBackupUtils.backup(tableNames, moduleName);
return backup;
}
还原
1.还原工具类
还原和备份差不多,不过要注意的是 要在linux 上面执行命令的话要加特殊的指令。
还有要注意的是 mysql -u 用户名 -p密码 。这里-p后面与密码直接不能有空格,否则会把你的密码当作数据库名
"/bin/bash","-c"
public class LinuxSqlImportUtils {
public static boolean exportSql(String fileName){
String user = "root";
//mysql -u forceview -pforceview 不能有空格,所以这样写
String password = "root";
String host = "192.168.16.2";
String exportDatabaseName = "audit";
String exportPath = "/usr/backupFile";
StringBuilder cmd = new StringBuilder();
cmd.append("mysql")
.append(" -u ")
.append(user)
.append(" -p")
.append(password)
.append(" ")
.append(exportDatabaseName)
.append(" < ")
.append(exportPath)
.append("/")
.append(fileName);
//执行命令
String[] command = {"/bin/bash","-c",cmd.toString()};
//使用拼接的方式来完成dos命令
//windows 用cmd/k
// String command = new String("cmd /k mysql"+" -h"+host+" -u"+user+" -p"+password+" "+exportDatabaseName+" <"+exportPath+"\\"+fileName);
// System.out.println(cmd.toString());
try {
Process process = Runtime.getRuntime().exec(command);
if (process.waitFor() == 0){
return true;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
}
前端
前端我是这样想的,使用一个选择框,选择你想导出的模块,这样选择后就会查出属于某个模块的表,再把表名后模块名传过去;
备份
<el-row >
<el-form :inline="true" label-width="100px" >
<el-form-item label="数据备份表">
<el-select
v-model="queryParams.tableModule"
@change="changeSelect()"
placeholder="请选择模块"
>
<el-option
v-for="item in selectChangeList"
:key="item.id"
:label="item.tableModule"
:value="item.tableModule"
>
</el-option>
</el-select>
</el-form-item>
</el-form>
</el-row>
比如选择aaa模块进行备份,备份后就会生成一个sql文件在你所指定的路径中,我下面实在window测试的,linux也测试过了。
js代码
//备份按钮
handleBackupModule(){
this.$modal.confirm('是否确认备份该模块的表数据?')
.then(()=>{
backupModule(this.sysDataBackupList).then(response=>{
if(response.code == 200){
this.$message({
message: "备份成功",
type: "success"
});
this.getFileTable();
}else{
this.$message({
message: "备份失败",
type: "error"
});
this.getFileTable();
}
});
}).catch(()=>{
this.$message({
message: "取消备份",
type: "info"
});
this.getFileTable();
});
},
还原 及 下载到本地
还原的话就在 查出来的 sql文件 表格后面加一个还原按钮就好,这两个就不详细描述了。
下载到本地的话用的若依自带的download方法。
//还原
handleRetoreFile(row){
this.$modal.confirm('是否确认还原该sql文件?')
.then(()=>{
restoreFile(row.fileName).then(res=>{
if(res.code == 200){
this.$message({
message: "还原成功",
type: "success"
});
this.getFileTable();
}else{
this.$message({
message: "还原失败",
type: "error"
});
this.getFileTable();
}
});
}).catch(()=>{
this.$message({
message: "取消还原",
type: "info"
});
})
},
//下载
handleDownloadFile(row){
const fileName = row.fileName
this.download(
"/dataBackup/downloadFile?fileName=" + fileName,
{
...fileName,
},
fileName
);
},
下载业务层
@Override
public boolean downloadFile(String fileName, HttpServletRequest request, HttpServletResponse response) {
String filePath = path + "/" +fileName;
File file = new File(filePath);
response.setCharacterEncoding("UTF-8");
String realFilename = file.getName();
response.setHeader("content-type", "application/octet-stream;charset=UTF-8");
response.setContentType("application/octet-stream;charset=UTF-8");
if (file.exists()){
try {
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFilename.trim(), "UTF-8"));
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes,0,bytes.length)) != -1){
outputStream.write(bytes,0,len);
}
outputStream.close();
inputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
上传
上传的话用的elmentui 上的上传组件,贴一下前后端
html
<el-dialog
title="导入"
:visible.sync="FileTable.upload.open"
width="400px"
append-to-body
>
<el-upload
ref="upload"
:limit="1"
accept=".sql"
:headers="FileTable.upload.headers"
:action="FileTable.upload.url"
:disabled="FileTable.upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处,或<em>点击上传</em>
</div>
<div class="el-upload__tip text-center" slot="tip">
<div class="el-upload__tip" slot="tip">
<!-- <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 -->
</div>
<span>仅允许导入sql文件。</span>
<!-- <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link> -->
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFile">确 定</el-button>
<el-button @click="FileTable.upload.open = false">取 消</el-button>
</div>
</el-dialog>
data
FileTable: {
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 数据备份表模块表格数据
FileList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
fileName: ""
},
// 表单参数
form: {},
// 表单校验
rules: {
},
upload: {
// 是否显示弹出层(用户导入)
open: false,
// 弹出层标题(用户导入)
title: "",
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的用户数据
// updateSupport: 0,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址
url: "https://192.168.16.22/dataBackup/importFile",
},
js
//导入
importFile(){
this.FileTable.upload.open = true;
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.FileTable.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.FileTable.upload.open = false;
this.FileTable.upload.isUploading = false;
this.$refs.upload.clearFiles();
this.$alert(
"<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
response.msg +
"</div>",
"导入结果",
{ dangerouslyUseHTMLString: true }
);
this.getFileTable();
},
// 提交上传文件
submitFile() {
this.$refs.upload.submit();
},
上传的业务层
@Override
public boolean importFile(MultipartFile file) {
try {
InputStream inputStream = file.getInputStream();
String name = file.getOriginalFilename();
String newPath = path + "/" + name;
File uploadFile = new File(newPath);
byte[] buffer = new byte[2048];
int len = 0;
FileOutputStream outputStream = new FileOutputStream(uploadFile);
while ((len = inputStream.read(buffer)) != -1){
outputStream.write(buffer,0,len);
}
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}