百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

SpringBoot实现MySQL数据库自动备份管理系统

ztj100 2025-03-08 02:55 19 浏览 0 评论

最近写了一个 MySQL 数据库自动、手动备份管理系统开源项目,想跟大家分享一下,项目地址:

https://gitee.com/asurplus/db-backup

1、界面献上

登录界面

首页

实例管理

执行备份

任务管理

备份记录

2、引入依赖



    com.zaxxer
    HikariCP
    4.0.3



    mysql
    mysql-connector-java



    org.springframework.boot
    spring-boot-starter-quartz



    cn.dev33
    sa-token-quick-login
    1.30.0

3、Sa-Token-Quick-Login 快速登录插件

如果你开发了一个小系统,并不需要多用户登录,但是必须得有登录,你又不想写登录,那么用 Sa-Token-Quick-Login 快速登录插件 是你的不二选择,具体用法参考: 【SpringBoot】59、SpringBoot使用Sa-Token-Quick-Login插件快速登录认证

https://lizhou.blog.csdn.net/article/details/123571910

4、动态创建数据库连接

  • 1、拼接连接地址
/**
 * 拼接url
 *
 * @param host
 * @param port
 * @return
 */
public static String getUrl(String host, String port, String database) {
    if (StringUtils.isBlank(database)) {
        database = "mysql";
    }
    return "jdbc:mysql://" + host + ":" + port + "/" + database + "?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8";
}

我们通过 ip,端口就能动态创建数据库连接,因为每个数据库实例中都有 “mysql” 数据库,我们默认使用 “mysql” 数据库来创建连接

  • 2、获取数据库连接配置
/**
 * 数据库连接配置
 *
 * @param properties 数据库连接信息
 * @return
 */
public static HikariConfig getHikariConfig(DbProperties properties) {
    HikariConfig hikariConfig = new HikariConfig();
    hikariConfig.setDriverClassName(properties.getClassName());
    hikariConfig.setJdbcUrl(getUrl(properties.getHost(), properties.getPort(), properties.getDatabase()));

    hikariConfig.setUsername(properties.getUsername());
    hikariConfig.setPassword(properties.getPassword());

    hikariConfig.setMaximumPoolSize(2);
    hikariConfig.setMinimumIdle(1);
    hikariConfig.setAutoCommit(true);
    hikariConfig.setConnectionTestQuery("SELECT 1 FROM DUAL");
    hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
    hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
    hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
    return hikariConfig;
}
  • 3、创建数据源
/**
 * 创建数据源
 *
 * @param hikariConfig
 * @return
 */
public static HikariDataSource createDataSource(HikariConfig hikariConfig) {
    if (null == hikariConfig) {
        return null;
    }
    return new HikariDataSource(hikariConfig);
}

这样我们就能得到 HikariDataSource 数据源了,可以用来执行 SQL 语句,例如:查询实例中的数据库,查询数据库中的数据表,对数据表中的数据实现 “增删改查” 操作

  • 4、获取实例中的所有数据库
/**
 * 获取数据库信息
 */
public static List listDataBases(DataSource dataSource) {
    try {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        List databases = jdbcTemplate.query("SHOW DATABASES", new RowMapper() {
            public String mapRow(ResultSet rs, int rowNum) throws SQLException {
                return rs.getString(1);
            }
        }, null);
        return DbUtil.filterDatabase(databases);
    } catch (Exception e) {
        log.error("获取数据库信息失败:{}", e.getMessage());
    }
    return null;
}

使用 “show databases” SQL 语句,查询实例中的所有数据库信息,因为实例中包含 MySQL 的默认数据库,我们需要将其过滤掉,如下:

private static final List FILTER_DATABASE = Arrays.asList("information_schema", "mysql", "performance_schema", "sys");

public static List filterDatabase(List list) {
    if (CollectionUtil.isEmpty(list)) {
        return null;
    }
    List resList = new ArrayList<>();
    for (String item : list) {
        if (!FILTER_DATABASE.contains(item)) {
            resList.add(item);
        }
    }
    return resList;
}
  • 5、获取数据库中的所有数据表
/**
 * 获取数据表信息
 */
public static List listTables(DataSource dataSource) {
    try {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        List databases = jdbcTemplate.query("SHOW TABLES;", new RowMapper() {
            public String mapRow(ResultSet rs, int rowNum) throws SQLException {
                return rs.getString(1);
            }
        }, null);
        return databases;
    } catch (Exception e) {
        log.error("获取数据表信息失败:{}", e.getMessage());
    }
    return null;
}

使用 “show tables” SQL 语句,查询数据库中的所有数据表信息

5、数据库备份

  • 1、公共参数
/**
 * 项目路径
 */
public static final String PROJECT_PATH = System.getProperty("user.dir");

/**
 * 当前系统类型
 */
public static final String OS_NAME = System.getProperty("os.name");
  • 2、拼接备份数据库命令
/**
 * 拼接备份sql命令
 *
 * @param host     主机地址
 * @param port     端口
 * @param dbName   数据库名称
 * @param tables   表格名称
 * @param dataType 备份参数【0-表结构1-表数据 else 表结构+表数据】
 * @param username 用户名
 * @param password 密码
 * @param path     备份文件目录
 * @param fileName 备份文件名
 * @return
 */
public static String[] createBackupCommand(String host, String port, String dbName, String[] tables, Integer dataType, String username, String password, String path, String fileName) {
    String[] commands = new String[3];
    if (OS_NAME.startsWith("Win")) {
        commands[0] = "cmd.exe";
        commands[1] = "/c";
    } else {
        commands[0] = "/bin/sh";
        commands[1] = "-c";
    }
    // 拼接命令
    StringBuilder mysqldump = new StringBuilder();
    mysqldump.append("mysqldump");
    mysqldump.append(" --opt");

    // 用户,密码
    mysqldump.append(" --user=").append(username);
    mysqldump.append(" --password=").append(password);

    // ip,端口
    mysqldump.append(" --host=").append(host);
    mysqldump.append(" --port=").append(port);

    // 使用的连接协议,包括:tcp, socket, pipe, memory
    mysqldump.append(" --protocol=tcp");

    // 设置默认字符集,默认值为utf8
    mysqldump.append(" --default-character-set=utf8");
    // 在导出数据之前提交一个BEGIN SQL语句,BEGIN 不会阻塞任何应用程序且能保证导出时数据库的一致性状态
    mysqldump.append(" --single-transaction=TRUE");

    // 导出存储过程以及自定义函数
    mysqldump.append(" --routines");
    // 导出事件
    mysqldump.append(" --events");

    // 只备份表结构
    if (null != dataType) {
        if (0 == dataType) {
            mysqldump.append(" --no-data");
        }
        // 只备份表数据
        else if (1 == dataType) {
            mysqldump.append(" --no-create-info");
        }
    }

    // 数据库名
    mysqldump.append(" ").append(dbName);

    // 数据表名
    if (null != tables && 0 < tables.length for string item : tables mysqldump.append .appenditem mysqldump.append> ").append(path).append(fileName);

    commands[2] = mysqldump.toString();
    return commands;
}
  • 3、执行备份数据库命令
/**
 * @param host     主机地址
 * @param port     端口
 * @param dbName   数据库名称
 * @param tables   表格名称
 * @param dataType 备份参数【0-表结构1-表数据 else 表结构+表数据】
 * @param username 用户名
 * @param password 密码
 * @return
 */
public BackRespVO backup(String host, String port, String dbName, String[] tables, Integer dataType, String username, String password) {
    // 返回对象
    BackRespVO respVO = new BackRespVO();
    try {
        // 当前年月日
        String ymd = MysqlTool.getDate();
        // 文件目录
        String path = PROJECT_PATH + File.separator + "static" + File.separator + ymd + File.separator;
        // 文件名
        String fileName = IdUtil.fastSimpleUUID() + ".sql";
        // 创建文件
        File file = new File(path, fileName);
        // 路径不存在,则新建
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        // shell 命令脚本
        String[] commands = createBackupCommand(host, port, dbName, tables, dataType, username, password, path, fileName);

        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec(commands);
        // 备份成功
        if (process.waitFor() == 0) {
            respVO.setFile(file);
            return respVO;
        }
        // 备份失败
        else {
            InputStream is = process.getErrorStream();
            if (is != null) {
                BufferedReader in = new BufferedReader(new InputStreamReader(is, OS_NAME.startsWith("Win") ? "GBK" : "UTF-8"));
                String line;
                StringBuilder sb = new StringBuilder();
                while ((line = in.readLine()) != null) {
                    sb.append(line);
                }
                respVO.setMsg("【" + dbName + "】备份失败,原因:" + sb);
                respVO.setFile(file);
            }
        }
    } catch (Exception e) {
        respVO.setMsg("【" + dbName + "】备份失败,原因:" + e.getMessage());
    }
    return respVO;
}
  • 4、执行备份任务,并保存备份记录
/**
 * 异步执行备份任务,保存备份记录
 *
 * @param dbId         实例id
 * @param host         主机地址
 * @param port         端口
 * @param dbName       数据库名
 * @param tables       数据表名
 * @param dataType     备份参数【0-表结构1-表数据 else 表结构+表数据】
 * @param username     用户名
 * @param password     密码
 * @param startTime    开始时间
 * @param categoryEnum 备份类型(手动备份,自动备份)
 */
@Async
public void saveBackUp(Integer dbId, String host, String port, String dbName, String[] tables, Integer dataType, String username, String password, Date startTime, BackupCategoryEnum categoryEnum) {
    // 执行备份
    BackRespVO respVO = backup(host, port, dbName, tables, dataType, username, password);
    // 备份失败
    if (!respVO.isSuccess()) {
        if (null != respVO.getFile()) {
            respVO.getFile().delete();
        }
    }
    // 保存备份记录
    BackupLog backupLog = new BackupLog();
    backupLog.setDbId(dbId);
    backupLog.setCategory(categoryEnum.getMsg());
    backupLog.setDatabaseName(dbName);
    backupLog.setTablesName(StringUtils.join(tables, ","));
    backupLog.setDataType(dataType);
    backupLog.setStatus(respVO.isSuccess());
    backupLog.setMsg(respVO.getMsg());
    // 备份成功
    if (respVO.isSuccess()) {
        // 文件相对路径
        backupLog.setFilePath(respVO.getFile().getPath().replace(MysqlTool.PROJECT_PATH + File.separator, ""));
        backupLog.setFileSize(respVO.getFile().length());
    }
    // 开始时间
    backupLog.setStartTime(startTime);
    backupLog.setEndTime(new Date());
    backupLog.setSpendTime(backupLog.getEndTime().getTime() - backupLog.getStartTime().getTime());
    backupLogMapper.insert(backupLog);
}

我们采用异步执行的方式,因为备份过程可能会很漫长,执行备份了之后,得到备份的文件路径,保存在备份记录中,可以实时进行查看

6、添加自动备份定时任务

定时任务主要依赖 quartz,可以动态管理定时任务,非常方便

  • 1、定时任务管理工具类
import cn.hutool.core.collection.CollectionUtil;
import com.asurplus.entity.BackupTask;
import com.asurplus.mapper.BackupTaskMapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;

/**
 * quartz工具类
 *
 * @Author Asurplus
 */
@Slf4j
@Component
public class QuartzManager {

    /**
     * 参数传递key
     */
    public static final String PARAM_KEY = "params";

    /**
     * 执行任务类名
     */
    public static final String CLASS_NAME = "com.asurplus.job.DbBackupJob";

    /**
     * 程序调度器
     */
    @Autowired
    private Scheduler scheduler;
    @Resource
    private BackupTaskMapper backupTaskMapper;

    /**
     * 系统启动执行
     */
    @PostConstruct
    public void init() {
        List list = backupTaskMapper.selectList(null);
        if (CollectionUtil.isNotEmpty(list)) {
            for (BackupTask item : list) {
                try {
                    add(item.getId(), item.getCron(), item.getParam(), item.getStatus());
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
            }
        }
    }

    /**
     * 添加定时任务
     */
    public void add(Integer id, String cronExpression, String param, Boolean status) {
        try {
            // 构建job信息
            JobDetail jobDetail = JobBuilder.newJob(getClass(CLASS_NAME).getClass()).withIdentity(getKey(id)).usingJobData(PARAM_KEY, param).build();
            // 表达式调度构建器(即任务执行的时间)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            // 按新的cronExpression表达式构建一个新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getKey(id)).withSchedule(scheduleBuilder).build();
            // 创建定时任务
            scheduler.scheduleJob(jobDetail, trigger);
            // 停止
            if (!status) {
                stop(id);
            }
        } catch (Exception e) {
            log.error("添加定时任务失败:{}", e.getMessage());
        }
    }

    /**
     * 编辑定时任务
     */
    public void update(Integer id, String cronExpression, String param, Boolean status) {
        try {
            // 判断是否存在,存在先删除
            if (scheduler.checkExists(JobKey.jobKey(getKey(id)))) {
                scheduler.deleteJob(JobKey.jobKey(getKey(id)));
            }
            // 再创建
            add(id, cronExpression, param, status);
        } catch (Exception e) {
            log.error("修改定时任务失败:{}", e.getMessage());
        }
    }

    /**
     * 暂停任务
     */
    public void stop(Integer id) {
        try {
            scheduler.pauseJob(JobKey.jobKey(getKey(id)));
        } catch (SchedulerException e) {
            // 暂停定时任务失败
            log.error("暂停定时任务失败:{}", e.getMessage());
        }
    }

    /**
     * 恢复任务
     */
    public void start(Integer id) {
        try {
            scheduler.resumeJob(JobKey.jobKey(getKey(id)));
        } catch (SchedulerException e) {
            // 暂停定时任务失败
            log.error("启动定时任务失败:{}", e.getMessage());
        }
    }

    /**
     * 立即执行一次
     */
    public void run(Integer id) {
        try {
            scheduler.triggerJob(JobKey.jobKey(getKey(id)));
        } catch (SchedulerException e) {
            // 暂停定时任务失败
            log.error("执行定时任务失败:{}", e.getMessage());
        }
    }

    /**
     * 删除定时任务
     */
    public void delete(Integer id) {
        try {
            // 停止触发器
            scheduler.pauseTrigger(TriggerKey.triggerKey(getKey(id)));
            // 移除触发器
            scheduler.unscheduleJob(TriggerKey.triggerKey(getKey(id)));
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(getKey(id)));
        } catch (Exception e) {
            log.error("删除定时任务失败:{}", e.getMessage());
        }
    }

    /**
     * 根据类名获取类
     */
    private Job getClass(String className) throws Exception {
        Class class1 = Class.forName(className);
        return (Job) class1.newInstance();
    }

    /**
     * 拼接key
     *
     * @return
     */
    public String getKey(Integer id) {
        return "dbBackUp-" + id;
    }
}

包含对定时任务的 “增删改查” 操作,默认执行 job 为:
com.asurplus.job.DbBackupJob

2、备份任务

import com.alibaba.fastjson.JSONObject;
import com.asurplus.config.quartz.QuartzManager;
import com.asurplus.enums.BackupCategoryEnum;
import com.asurplus.utils.MysqlTool;
import com.asurplus.vo.BackupJobVO;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;

/**
 * 备份定时任务
 */
@Slf4j
public class DbBackupJob implements Job {

    @Autowired
    private MysqlTool mysqlTool;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 解析参数
        BackupJobVO vo = JSONObject.parseObject(context.getJobDetail().getJobDataMap().getString(QuartzManager.PARAM_KEY), BackupJobVO.class);
        // 开始备份
        mysqlTool.saveBackUp(vo.getDbId(), vo.getHost(), vo.getPort(), vo.getDbName(), vo.getTables(), vo.getDataType(), vo.getUsername(), vo.getPassword(), new Date(), BackupCategoryEnum.AUTO);
    }
}

获取到备份参数,包括:主机地址,端口,用户名,密码,数据库名等,就能执行备份任务了

好了,我们的数据库备份管理系统大致就介绍完了,如您在阅读中发现不足,欢迎留言!!!

相关推荐

其实TensorFlow真的很水无非就这30篇熬夜练

好的!以下是TensorFlow需要掌握的核心内容,用列表形式呈现,简洁清晰(含表情符号,<300字):1.基础概念与环境TensorFlow架构(计算图、会话->EagerE...

交叉验证和超参数调整:如何优化你的机器学习模型

准确预测Fitbit的睡眠得分在本文的前两部分中,我获取了Fitbit的睡眠数据并对其进行预处理,将这些数据分为训练集、验证集和测试集,除此之外,我还训练了三种不同的机器学习模型并比较了它们的性能。在...

机器学习交叉验证全指南:原理、类型与实战技巧

机器学习模型常常需要大量数据,但它们如何与实时新数据协同工作也同样关键。交叉验证是一种通过将数据集分成若干部分、在部分数据上训练模型、在其余数据上测试模型的方法,用来检验模型的表现。这有助于发现过拟合...

深度学习中的类别激活热图可视化

作者:ValentinaAlto编译:ronghuaiyang导读使用Keras实现图像分类中的激活热图的可视化,帮助更有针对性...

超强,必会的机器学习评估指标

大侠幸会,在下全网同名[算法金]0基础转AI上岸,多个算法赛Top[日更万日,让更多人享受智能乐趣]构建机器学习模型的关键步骤是检查其性能,这是通过使用验证指标来完成的。选择正确的验证指...

机器学习入门教程-第六课:监督学习与非监督学习

1.回顾与引入上节课我们谈到了机器学习的一些实战技巧,比如如何处理数据、选择模型以及调整参数。今天,我们将更深入地探讨机器学习的两大类:监督学习和非监督学习。2.监督学习监督学习就像是有老师的教学...

Python教程(三十八):机器学习基础

...

Python 模型部署不用愁!容器化实战,5 分钟搞定环境配置

你是不是也遇到过这种糟心事:花了好几天训练出的Python模型,在自己电脑上跑得顺顺当当,一放到服务器就各种报错。要么是Python版本不对,要么是依赖库冲突,折腾半天还是用不了。别再喊“我...

超全面讲透一个算法模型,高斯核!!

...

神经网络与传统统计方法的简单对比

传统的统计方法如...

AI 基础知识从0.1到0.2——用“房价预测”入门机器学习全流程

...

自回归滞后模型进行多变量时间序列预测

下图显示了关于不同类型葡萄酒销量的月度多元时间序列。每种葡萄酒类型都是时间序列中的一个变量。假设要预测其中一个变量。比如,sparklingwine。如何建立一个模型来进行预测呢?一种常见的方...

苹果AI策略:慢哲学——科技行业的“长期主义”试金石

苹果AI策略的深度原创分析,结合技术伦理、商业逻辑与行业博弈,揭示其“慢哲学”背后的战略智慧:一、反常之举:AI狂潮中的“逆行者”当科技巨头深陷AI军备竞赛,苹果的克制显得格格不入:功能延期:App...

时间序列预测全攻略,6大模型代码实操

如果你对数据分析感兴趣,希望学习更多的方法论,希望听听经验分享,欢迎移步宝藏公众号...

AI 基础知识从 0.4 到 0.5—— 计算机视觉之光 CNN

...

取消回复欢迎 发表评论: