纯手工编写《数据库异地备份还原工具》(建议收藏)

Posted 洛阳泰山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了纯手工编写《数据库异地备份还原工具》(建议收藏)相关的知识,希望对你有一定的参考价值。

前言

网上百度很多集成到项目里的数据库备份方法,大多数需要通过调用 mysqldump.exe和mysql.exe来完成备份和恢复的工作,所以只能项目和mysql在同一台服务器上才能实现备份还原,不符合我的需求,且支持的数据库类型太单一,于是,自己设计了一个数据备份工具。

设计思路

 实际应用中,项目一旦部署,数据库表结构就不会发生变化,我们事先整理好了建表sql文档,只需要备份,表里面的数据就可以。所以我的思路是

查询所有表的数据,转化成sql的插入语句。

一句话,代表整篇文章的核心思想

怎么做? 

pom文件所需要依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/>
    </parent>
   <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

数据库备份还原工具类 


import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 数据库数据备份
 *
 * @author tarzan Liu
 * @date 2021/7/20 19:44
 */
@Slf4j
@Component
public class DbBackupTools {
    @Resource
    private  JdbcTemplate jdbcTemplate;
    @Getter
    private  String filePrefix="backupSql_";
    @Getter
    private  String sqlBackupPath;
    static   SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //当前系统最佳线程数
    private int curSystemThreads=1;

    @PostConstruct
    private void  init(){
        // 获取Java虚拟机的可用的处理器数,最佳线程个数,处理器数*2。根据实际情况调整
        curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
        //初始化备份路劲
        sqlBackupPath=getBackupPath();
    }

    //获取所有表名称
    private  List<String> tableNames() {
        List<String> tableNames= new ArrayList<>();
        try {
            Connection getConnection=jdbcTemplate.getDataSource().getConnection();
            DatabaseMetaData metaData = getConnection.getMetaData();
            ResultSet rs = metaData.getTables(getConnection.getCatalog(), null, null, new String[] { "TABLE" });
            while (rs.next()) {
                String tableName=rs.getString("TABLE_NAME");
                tableNames.add(tableName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return tableNames;
    }

    //数据还原
    public synchronized  boolean rollback(String fileName) {
        Long stat=System.currentTimeMillis();
        List<String> list=new ArrayList<>();
        //清空所有表数据
        tableNames().forEach(t->{
            list.add("TRUNCATE "+t+";");
        });
        jdbcTemplate.batchUpdate(list.toArray(new String[list.size()]));
        list.clear();
        try {
            FileInputStream out = new FileInputStream(sqlBackupPath+fileName);
            InputStreamReader reader = new InputStreamReader(out, StandardCharsets.UTF_8);
            BufferedReader in = new BufferedReader(reader);
            String line;
            while ((line = in.readLine()) != null) {
                list.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        executeAsync(list);
        log.info("恢复数据耗时 "+(System.currentTimeMillis()-stat)+" ms");
        return true;
    }

    //异步多线程处理
    private void executeAsync(List<String> sqlList) {
        int pages=0;
        int pageNum=1000;
        ExecutorService es = Executors.newFixedThreadPool(curSystemThreads);
        while(true){
            pages++;
            int endIndex = Math.min(pages * pageNum, sqlList.size());
            int finalPages = pages;
            Thread thread= new Thread(() ->{
                List<String> list=sqlList.subList((finalPages - 1) * pageNum, endIndex);
                jdbcTemplate.batchUpdate(list.toArray(new String[list.size()]));
                log.info("恢复数据"+list.size()+"条sql完毕。。。。。。");
            });
            es.execute(thread);
            if(endIndex>=sqlList.size()){
                break;
            }
        }
    }

    //数据备份
    public synchronized boolean backSql() {
        Long stat=System.currentTimeMillis();
        try {
            File dir = new File(sqlBackupPath);
            dir.mkdirs();
            String path = dir.getPath() + "/"+ filePrefix+System.currentTimeMillis()+".sql" ;
            File file = new File(path);
            if (!file.exists()){
                file.createNewFile();
            }
            tableNames().forEach(t->{
               StringBuilder sb=new StringBuilder();
                List<Map<String, Object>> list=jdbcTemplate.queryForList("select * from "+t);
                list.forEach(e->{
                    sb.append("INSERT INTO "+t+ " VALUES (");
                    e.forEach((k,v)->{
                        if(v instanceof String){
                            if (((String) v).contains("\\n")) {
                                v = ((String) v).replaceAll("\\n", "\\\\\\\\n");
                            }
                            if (((String) v).contains("\\r")) {
                                v = ((String) v).replaceAll("\\r", "\\\\\\\\r");
                            }
                            sb.append("'" + v + "'" + ",");
                        }else if(v instanceof Date){
                            sb.append("'"+format.format(v)+"'"+",");
                        }else{
                            sb.append(v+",");
                        }
                    });
                    sb.append("); \\n");
                });
                String sql= sb.toString().replace(",);",");");
                try{
                FileOutputStream out = new FileOutputStream(file, true); //如果追加方式用true
                out.write(sql.getBytes("utf-8"));//注意需要转换对应的字符集
                out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        log.info("备份文件耗时 "+(System.currentTimeMillis()-stat)+" ms");
        return true;
    }

    //获得备份路径 ,jar包启动备份文件夹在同级目录
    private  String getBackupPath() {
        String classPath = DbBackupTools.class.getResource("/").getPath();
        if (classPath.indexOf(".jar") > 0) {
            classPath = classPath.substring(0, classPath.lastIndexOf(".jar"));
            classPath = classPath.substring(6, classPath.lastIndexOf("/"));
            String sqlBackupPath = File.separator + classPath + "dbBackup/";
            log.info("========数据备份路径:" + sqlBackupPath + "========");
            return sqlBackupPath;
        } else {
            String sqlBackupPath = classPath + "dbBackup/";
            log.info("========数据备份路径:" + sqlBackupPath + "========");
            return sqlBackupPath;
        }
    }


}

数据库工具使用实例

    /* 备份数据库 */
    @PostMapping("backupSQL")
    @ResponseBody
    public ResponseVo backupSQL(){
        if (dbTools.backSql()) {
            return ResultUtil.success("数据备份成功");
        } else {
            return ResultUtil.error("数据备份失败");
        }
    }

    /* 备份文件列表 */
    @PostMapping("backupList")
    @ResponseBody
    public PageResultVo backupList(Integer pageNumber, Integer pageSize){
        File file=new File(dbTools.getBackupPath());
        File[] files= file.listFiles();
        if(files==null){
            return ResultUtil.table(null,null);
        }
        List<DbBackupVO> list= Lists.newArrayList();
        Arrays.asList(files).forEach(e->{
            DbBackupVO vo=new DbBackupVO();
            vo.setFileName(e.getName());
            vo.setSize(e.length());
            vo.setCreateTime(new Date(e.lastModified()));
            list.add(vo);
        });
        Collections.reverse(list);
        int endIndex = Math.min(pageNumber * pageSize, list.size());
        return ResultUtil.table(list.subList((pageNumber - 1) * pageSize, endIndex), (long) list.size());
    }

    /*删除备份*/
    @PostMapping("/delete")
    @ResponseBody
    public ResponseVo deleteRole(String fileName) {
        File file=new File(dbTools.getBackupPath()+fileName);
        if (file.delete()) {
            return ResultUtil.success("删除备份成功");
        } else {
            return ResultUtil.error("删除备份失败");
        }
    }

    /*还原备份*/
    @PostMapping("rollback")
    @ResponseBody
    public ResponseVo rollback(String fileName){
        if ( dbTools.rollback(fileName)) {
            return ResultUtil.success("数据恢复成功");
        } else {
            return ResultUtil.error("数据恢复失败");
        }
    }

页面效果

注:如需转载,请注明作者和原文链接,否则必追究。

以上是关于纯手工编写《数据库异地备份还原工具》(建议收藏)的主要内容,如果未能解决你的问题,请参考以下文章

十分钟掌握 Flink CDC,实现Mysql数据增量备份到Clickhouse [纯干货,建议收藏]

十分钟掌握 Flink CDC,实现Mysql数据增量备份到Clickhouse [纯干货,建议收藏]

数据库异地备份还原

(4.2)mysql备份还原——备份概述

rsync远程同步及rsync+inotify实时同步

达梦数据库备份-手动备份