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

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

ztj100 2025-03-08 02:55 16 浏览 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);
    }
}

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

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

相关推荐

30天学会Python编程:16. Python常用标准库使用教程

16.1collections模块16.1.1高级数据结构16.1.2示例...

强烈推荐!Python 这个宝藏库 re 正则匹配

Python的re模块(RegularExpression正则表达式)提供各种正则表达式的匹配操作。...

Python爬虫中正则表达式的用法,只讲如何应用,不讲原理

Python爬虫:正则的用法(非原理)。大家好,这节课给大家讲正则的实际用法,不讲原理,通俗易懂的讲如何用正则抓取内容。·导入re库,这里是需要从html这段字符串中提取出中间的那几个文字。实例一个对...

Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)

实现功能:Python数据分析实战-利用正则表达式提取文本中的URL网址和邮箱...

python爬虫教程之爬取当当网 Top 500 本五星好评书籍

我们使用requests和re来写一个爬虫作为一个爱看书的你(说的跟真的似的)怎么能发现好书呢?所以我们爬取当当网的前500本好五星评书籍怎么样?ok接下来就是学习python的正确姿...

深入理解re模块:Python中的正则表达式神器解析

在Python中,"re"是一个强大的模块,用于处理正则表达式(regularexpressions)。正则表达式是一种强大的文本模式匹配工具,用于在字符串中查找、替换或提取特定模式...

如何使用正则表达式和 Python 匹配不以模式开头的字符串

需要在Python中使用正则表达式来匹配不以给定模式开头的字符串吗?如果是这样,你可以使用下面的语法来查找所有的字符串,除了那些不以https开始的字符串。r"^(?!https).*&...

先Mark后用!8分钟读懂 Python 性能优化

从本文总结了Python开发时,遇到的性能优化问题的定位和解决。概述:性能优化的原则——优化需要优化的部分。性能优化的一般步骤:首先,让你的程序跑起来结果一切正常。然后,运行这个结果正常的代码,看看它...

Python“三步”即可爬取,毋庸置疑

声明:本实例仅供学习,切忌遵守robots协议,请不要使用多线程等方式频繁访问网站。#第一步导入模块importreimportrequests#第二步获取你想爬取的网页地址,发送请求,获取网页内...

简单学Python——re库(正则表达式)2(split、findall、和sub)

1、split():分割字符串,返回列表语法:re.split('分隔符','目标字符串')例如:importrere.split(',','...

Lavazza拉瓦萨再度牵手上海大师赛

阅读此文前,麻烦您点击一下“关注”,方便您进行讨论和分享。Lavazza拉瓦萨再度牵手上海大师赛标题:2024上海大师赛:网球与咖啡的浪漫邂逅在2024年的上海劳力士大师赛上,拉瓦萨咖啡再次成为官...

ArkUI-X构建Android平台AAR及使用

本教程主要讲述如何利用ArkUI-XSDK完成AndroidAAR开发,实现基于ArkTS的声明式开发范式在android平台显示。包括:1.跨平台Library工程开发介绍...

Deepseek写歌详细教程(怎样用deepseek写歌功能)

以下为结合DeepSeek及相关工具实现AI写歌的详细教程,涵盖作词、作曲、演唱全流程:一、核心流程三步法1.AI生成歌词-打开DeepSeek(网页/APP/API),使用结构化提示词生成歌词:...

“AI说唱解说影视”走红,“零基础入行”靠谱吗?本报记者实测

“手里翻找冻鱼,精心的布局;老漠却不言语,脸上带笑意……”《狂飙》剧情被写成歌词,再配上“科目三”背景音乐的演唱,这段1分钟30秒的视频受到了无数网友的点赞。最近一段时间随着AI技术的发展,说唱解说影...

AI音乐制作神器揭秘!3款工具让你秒变高手

在音乐创作的领域里,每个人都有一颗想要成为大师的心。但是面对复杂的乐理知识和繁复的制作过程,许多人的热情被一点点消磨。...

取消回复欢迎 发表评论: