Java中的Mysql数据库备份与定时任务快速实现(详细代码示例)

news2025/1/31 2:52:52

引言

在现代软件系统中,数据库备份是确保数据安全的关键措施之一。通过定期备份,可以在数据丢失或损坏时迅速恢复,从而减少潜在的业务风险。Java作为一种广泛使用的编程语言,提供了多种实现数据库备份的方法。本文将介绍如何使用Java编写一个定时备份数据库的程序,并详细解释每个步骤的实现细节。

一、开发环境

  1. SpringBoot项目
  2. 项目中添加MySQL的JDBC驱动依赖
<!-- mysql 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

二、代码实现

1. 创建定时任务类

首先,我们创建一个名为BackUpDatabase的类,并使用@Component@EnableScheduling注解将其标记为Spring组件并启用定时任务功能。同时,使用@Slf4j注解来简化日志记录。

注意:直接在代码中硬编码数据库的用户名和密码是不安全的。在实际生产环境中,应该使用更安全的方式来管理这些敏感信息,例如使用环境变量、配置文件或密钥管理服务。这里为了方便测试和展示。

import java.io.*;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import lombok.extern.slf4j.Slf4j;
@Component
@EnableScheduling
@Slf4j
public class BackUpDatabase {
    // 数据库连接信息
    private static final String DB_URL = "jdbc:mysql://localhost:3306/yourDataBase?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&allowMultiQueries=true&useSSL=false";
    private static final String DATABASE = "yourDataBase";
    private static final String USER = "yourUserName";
    private static final String PASSWORD = "yourPwd";

    // 备份文件存储路径
    private static final String BACKUP_WIN_DIR = "D:/yourWinPath";
    private static final String BACKUP_LINUX_DIR = "yourLinuxPath";
    private static final String BACKUP_FILE = "database_backup.sql";

    // 保留备份文件的天数
    private static final int LIMIT_LENGTH = 30;

    // 定时任务方法,每天凌晨3点执行
    @Scheduled(cron = "0 0 3 * * *")
    public void backUp() {
        // 执行备份操作
    }
}

2. 实现备份文件存储逻辑

backUp方法中,我们实现数据库备份的逻辑。

  • 根据操作系统类型确定备份文件的存储路径。(作用:方便开发环境是Windows系统的,好调试程序)
  • 检查备份文件夹是否存在,如果不存在则创建。
  • 获取文件夹中的所有备份文件(:有必要的话,可以添加对文件名进行格式匹配过滤后再操作),并根据最后修改时间对文件进行排序。如果文件数量超过设定的限制,则删除最早的文件。
public void backUp() {
    String osName = System.getProperty("os.name");
    String directoryPath;
    // 更精确的时间戳作为前缀
    String backupFileNamePrefix = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    String backupFileName = backupFileNamePrefix + "_" + BACKUP_FILE;

    if (osName.startsWith("Windows")) {
        directoryPath = BACKUP_WIN_DIR;
    } else {
        directoryPath = BACKUP_LINUX_DIR;
    }

    // 检查文件夹是否存在,如果不存在则创建
    File directory = new File(directoryPath);
    if (!directory.exists()) {
        directory.mkdirs();
    }

    // 获取文件夹中的所有文件
    File[] files = directory.listFiles();
    if (files != null && files.length >= LIMIT_LENGTH) {
        // 根据最后修改时间对文件进行排序
        Arrays.sort(files, Comparator.comparingLong(File::lastModified));

        // 删除最早的文件(即排序后的第一个文件)
        for (File file : files) {
            if (!file.isDirectory()) {
                file.delete();
                break; // 只删除一个文件
            }
        }
    }

    String filePath = directoryPath + File.separator + backupFileName;

    // 执行实际的备份操作
    // ...
}

3. 执行数据库备份操作

  • 建立与数据库的稳定连接,并初始化BufferedWriter对象,为后续的备份文件写入做好准备。在确保连接成功的基础上,程序进一步获取数据库内的所有表名,并将这些表名有序地存储在一个列表中。

  • 遍历这个表名列表,对每个表名调用backupTable方法,同时传递数据库连接、BufferedWriter对象和当前表名作为参数,以便执行具体的备份操作。当所有表都成功备份后,程序会记录一条成功的消息,通知用户备份过程已完成。

在整个备份过程中,若遇到连接失败、获取表名出错或备份执行错误等异常情况,程序都会及时捕获这些异常,并记录详细的错误日志。整个备份流程都置于try-with-resources语句块中,确保所有资源在备份完成后都能得到正确的关闭,从而避免资源泄露的问题。

public void backUp() {
    // ...之前的代码省略...
    // 记录所有表名
    List<String> tableNames = new ArrayList<>();
    // 1. 建立数据库连接
    try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
         BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
        // 2. 获取所有表名
        DatabaseMetaData metaData = conn.getMetaData();
        try (ResultSet tables = metaData.getTables(DATABASE, null, null, new String[]{"TABLE"})) {
            while (tables.next()) {
                tableNames.add(tables.getString("TABLE_NAME"));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        // 关闭原始ResultSet后进行备份
        for (String tableName : tableNames) {
            backupTable(conn, writer, tableName);
        }
        log.info("数据库备份成功完成,备份文件名:" + backupFileName);

    } catch (SQLException | IOException e) {
        log.error("数据库备份失败", e);
    }
}

4. backupTable方法的实现

  • 通过数据库连接获取数据库元数据,然后使用这些元数据来获取指定表的列信息。程序将每一列的信息(如名称、类型、大小等)写入到一个缓冲区中,格式化成SQL语句,用来重新创建这个表,包括表结构的创建(列的定义)和表数据的填充。

  • 在处理过程中,使用了一个HashSet来避免处理重复的列名。对于每一列,根据其类型、是否自增、默认值、备注等属性生成相应的SQL语句片段。

  • 程序还处理了表的主键,设计表字段的结尾添加主键字段id指定。

  • 如果表中存在数据,调用外部定义的writeTableData方法将数据也备份到文本中。整个流程首先删除已存在的表(如果存在),然后创建表,并可能填充数据。

private void backupTable(Connection conn, BufferedWriter writer, String tableName) throws SQLException, IOException {
    DatabaseMetaData dbMetaData = conn.getMetaData();
    // 用于跟踪处理过的列名,避免重复
    HashSet<String> processedColumns = new HashSet<>();

    // 获取列信息
    try (ResultSet columns = dbMetaData.getColumns(DATABASE, null, tableName, null)) {
        writer.write("-- Creating table " + tableName + "\n");
        writer.write("DROP TABLE IF EXISTS " + tableName + ";\n");
        writer.write("CREATE TABLE " + tableName + " (\n");
        // 用于跟踪是否是第一个列
        boolean isFirstColumn = true;

        while (columns.next()) {
            String columnName = columns.getString("COLUMN_NAME");
            if (processedColumns.add(columnName)) {
                if (!isFirstColumn) {
                    writer.write(",\n");
                } else {
                    isFirstColumn = false;
                }

                String dataType = columns.getString("TYPE_NAME");
                int dataSize = columns.getInt("COLUMN_SIZE");
                int decimalDigits = columns.getInt("DECIMAL_DIGITS");
                int nullable = columns.getInt("NULLABLE");
                String isAutoIncrement = columns.getString("IS_AUTOINCREMENT");
                String defaultValue = columns.getString("COLUMN_DEF");
                String remarks = columns.getString("REMARKS");
                writer.write(String.format("  `%s` %s", columnName, dataType));

                // 特殊类型处理,不需要添加类型长度
                if (dataType.equalsIgnoreCase("DATETIME") || dataType.equalsIgnoreCase("TIMESTAMP") || dataType.equalsIgnoreCase("DATE") || dataType.equalsIgnoreCase("TEXT")) {
                    if (decimalDigits > 0 && decimalDigits <= 6) {
                        writer.write("(" + decimalDigits + ")");
                    }
                } else {
                    if (dataSize > 0) {
                        writer.write("(" + dataSize);
                        if (decimalDigits > 0) {
                            writer.write(", " + decimalDigits);
                        }
                        writer.write(")");
                    }
                }

                if ("YES".equalsIgnoreCase(isAutoIncrement)) {
                    writer.write(" AUTO_INCREMENT");
                } else {
                    writer.write(nullable == DatabaseMetaData.columnNoNulls ? " NOT NULL" : " NULL");
                }

                if (defaultValue != null) {
                    writer.write(" DEFAULT '" + defaultValue + "'");
                }

                if (remarks != null && !remarks.isEmpty()) {
                    writer.write(" COMMENT '" + remarks + "'");
                }
            }
        }

        // 处理主键
        writer.write(",\n PRIMARY KEY (`id`) USING BTREE");
        // 结束表定义
        writer.write("\n);\n\n");
    }

    // 填充表数据,这里调用一个外部定义的方法 `writeTableData`
    if (hasData(conn, tableName)) {
        writeTableData(conn, writer, tableName);
    }
}

5. 辅助方法的实现

两个辅助方法用于处理数据库表数据的备份。

  • hasData方法检查指定表中是否含有数据。
  • writeTableData方法负责从目标表查询数据并格式化为SQL插入语句。使用SELECT * FROM tableName收集数据,然后迭代结果集,将每条数据转换为SQL插入格式并写入缓冲器,处理字符串和日期格式的引号,并在最后一条数据后结束SQL语句。这样,表的数据被逐条转换并保存为可执行的SQL插入命令。
private boolean hasData(Connection conn, String tableName) throws SQLException {
    try (Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM " + tableName)) {
        if (rs.next()) {
            return rs.getInt(1) > 0;
        }
        return false;
    }
}
private void writeTableData(Connection conn, BufferedWriter writer, String tableName) throws SQLException, IOException {
    String selectQuery = "SELECT * FROM " + tableName;
    try (Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(selectQuery)) {
        ResultSetMetaData rsmd = rs.getMetaData();

        boolean firstRow = true;
        while (rs.next()) {
            if (firstRow) {
                writer.write("INSERT INTO " + tableName + " VALUES \n");
                firstRow = false;
            } else {
                writer.write(",\n");
            }
            writer.write("(");
            for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                Object value = rs.getObject(i);
                if (value != null) {
                    String valueString = value.toString().replace("'", "''");
                    if (value instanceof String || value instanceof Timestamp || value instanceof Date) {
                        writer.write("'" + valueString + "'");
                    } else {
                        writer.write(valueString);
                    }
                } else {
                    writer.write("NULL");
                }
                if (i < rsmd.getColumnCount()) {
                    writer.write(", ");
                }
            }
            writer.write(")");
        }
        // 如果至少写入了一行数据,才需要结束SQL语句
        if (!firstRow) {
            writer.write(";\n\n");
        }
    }
}

四、测试结果

兼容开发环境(Windows)和生产环境(Linux),所以类中创建main方法直接测试:
通过日志打印成功完成,然后到你的备份文件存储路径下,发现备份文件已生成。
在这里插入图片描述

五、注意事项

  1. 安全性:直接在代码中硬编码数据库的用户名和密码是不安全的。在实际生产环境中,应该使用更安全的方式来管理这些敏感信息,例如使用环境变量、配置文件或密钥管理服务。
  2. 错误处理:上述代码示例中简化了错误处理逻辑。在实际应用中,应该更详细地处理可能出现的各种异常,确保程序的健壮性。
  3. 权限管理:确保执行备份的账户有足够的权限来访问数据库和文件系统。

六、总结

通过结合Java的定时任务功能和数据库备份逻辑,我们可以轻松地实现数据库的自动备份。在实际应用中,还需要考虑备份的安全性、性能、可靠性以及恢复策略等多个方面。此外,定期测试备份文件的完整性和可恢复性也是非常重要的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1653326.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「网络流 24 题」太空飞行计划 【最大权值闭合图】

「网络流 24 题」太空飞行计划 题意 有 n n n 个实验 和 m m m 个器械&#xff0c;每个实验都需要若干个指定的器械才能进行 实验 i i i 的盈利为 p i p_i pi​&#xff0c; 器械 j j j 的花销为 c j c_j cj​ 找出纯利润最大的实验计划 思路 这是非常典型的最大权值…

独家专访辉羲智能章健勇:数据闭环定义芯片,帮车厂造中国版FSD

‍采访、编辑 |德新 撰文 |苗岭 辉羲智能&#xff0c;智能驾驶芯片行业最新的进入者。 这家公司成立于2022年&#xff0c;今年辉羲即将发布它的首款高阶智驾芯片。而另外两家智驾计算平台的头部公司地平线和黑芝麻已经在前不久分别向港交所提交了IPO申请。 国内的自动驾驶行…

【北京迅为】《iTOP-3588开发板快速烧写手册》-第8章 TF启动

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

论文笔记模版

1. 摘要 1.1 背景 1.2 挑战 1.3 提出新方法 1.4 贡献 2. 引言 2.1 背景&#xff08;引出问题&#xff09; ①介绍大背景&#xff1a; ② 应用场景&#xff1a; ③ 介绍主题&#xff1a; 2.2 引出挑战 一般用图表来展现出我们的挑战&#xff08;直观&#xff0c;解决什…

速卖通揭秘:aliexpress.item_get API商品详情返回值全解析

速卖通&#xff08;AliExpress&#xff09;是阿里巴巴旗下的一个面向全球市场的B2C电商平台&#xff0c;为卖家提供了一个向全球消费者销售商品的平台。对于开发者来说&#xff0c;速卖通提供了API接口来方便地进行数据交互和集成。其中&#xff0c;item_get API是用于获取商品…

java报错:使用mybatis plus查询一个只返回一条数据的sql,却报错返回了1000多条

今天遇到一个问题 系统线上问题&#xff0c;经常出现这样的问题&#xff0c;刚重启系统时不报错了&#xff0c;可是运行一段时间又会出现。sql已经写了limit 1&#xff0c;mybatis的debug日志也返回total为1&#xff0c;可是却报错返回了1805条数据 乍一看&#xff0c;感觉太不…

【FTP】配置FTP服务器并访问测试(已更新)

1.之前访问搭建的FTP服务器&#xff0c;有些现场环境未搭建&#xff0c;在数据处理过程中遇到一些阻力&#xff0c;多有不便之处。 2.熟悉、梳理、总结下FTP服务器相关知识体系 3.欢迎批评指正&#xff0c;跪谢一键三连&#xff01; 基于Linux配置FTP服务器并访问测试文章目录索…

基于V4L2框架的摄像头从上层到底层开发

文章目录 一、V4L2应用开发1、识别摄像头2、查看摄像头设备的能力3、查看支持视频格式4、设置视频格式5、申请帧缓冲6、启动采集7、出队取一帧图像8、入队归还帧缓冲9、停止视频采集10、退出释放资源 二、V4L2框架源码分析1、struct video_device2、struct v4l2_device *v4l2_d…

unity ui 同屏

一共有三个摄像机&#xff0c;上屏&#xff0c;下屏 和 类似照相机的ccamera 类似照相机的ccamera的设置&#xff1a; 下屏摄像机设置&#xff1a; 下屏交互的Canvas设置&#xff1a; 新建一个canvas&#xff0c;下面放上rawimage&#xff1a; 如果下屏不想显示的内容&#xf…

【前端】实现快速改变内容大小选择框

简言 简单实现选择框改变内容大小和位置。 内容 这里实现选择框改变内容大小是让内容宽高等于选择框的百分之百&#xff0c;当选择框大小改变时&#xff0c;内容也会响应的改变。 位置则是根据定位实现的。 选择框 选择框就是一个div&#xff0c;然后定位上下左右四条边和…

Stable Diffusion【古风模型】:喜欢古风的看过来,超写实汉服兼顾现代风格大模型汉服国风桃夭

这次来介绍【Stable Diffusion【古风模型】&#xff1a;喜欢古风的看过来&#xff0c;超写实汉服兼顾现代风格大模型汉服国风桃夭】&#xff0c;对于汉服国风桃妖大模型&#xff0c;不仅在古装国风写实上表现出色&#xff0c;同时该模型也兼容现代风格&#xff0c;并且出图效果…

测试人员在面试时的注意事项

一、技术方面面试 在某种程度上来说&#xff0c;技术面试重要到能够决定你是否被聘用。在技术岗位方面&#xff0c;在个人品德没有问题的前提下&#xff0c;招聘公司对技术是最关心的。 我现在并不能给你分析具体的面试题&#xff0c;因为与笔试题相比&#xff0c;面试题千变万…

信创基础硬件之芯片

信创基础硬件之芯片 文章目录 信创基础硬件之芯片服务器服务器的定义服务器的功能服务器的构成服务器的性能 处理器&#xff08;CPU&#xff09;CPUGPUDPU CPU的分类按CPU指令集架构分类按CPU体系架构分类 CPU产业链六大国产CPU公司详解海光飞腾鲲鹏兆芯龙芯申威 国产CPU对比从…

自动群发国际短信脚本的详情介绍!

在当今全球化的商业环境中&#xff0c;信息的及时传递显得尤为重要&#xff0c;国际短信作为一种高效、低成本的沟通方式&#xff0c;被广泛应用于企业营销、客户服务、产品推广等领域。 为了满足企业对于群发国际短信的需求&#xff0c;市场上涌现出了许多自动群发国际短信脚…

Golang编译优化——稀疏条件常量传播

文章目录 一、概述二、稀疏条件常量传播2.1 初始化worklist2.2 构建def-use链2.3 更新值的lattice2.4 传播constant值2.5 替换no-constant值 一、概述 常量传播&#xff08;constant propagation&#xff09;是一种转换&#xff0c;对于给定的关于某个变量 x x x和一个常量 c …

初探MFC程序混合使用QT

一、背景 随着操作系统国产化替代的趋势越发明显&#xff0c;软件支持国际化、跨平台&#xff0c;已然是必须做的一件事情。原有的软件UI层用的是MFC&#xff0c;将其换成QT&#xff0c;想必是一种较好的方案。对于大型软件&#xff0c;特别是已发布&#xff0c;但还处于不断迭…

43.乐理基础-拍号-常见的拍号与强弱关系

首先拍号的定义&#xff1a;39.认识音符、40.什么是一拍、41.小节、小节线、终止线、42.看懂拍号的意义 通过 39.认识音符、40.什么是一拍、41.小节、小节线、终止线、42.看懂拍号的意义 应该可以知道 Y的取值只能是2、4、8、16、32、64。。。。因为Y指的是Y分音符&#xff0c;…

数据库数据恢复—Sql Server数据库文件丢失丢失怎么恢复数据?

数据库数据恢复环境&#xff1a; 5块硬盘组建一组RAID5阵列&#xff0c;划分LUN供windows系统服务器使用。windows系统服务器内运行了Sql Server数据库&#xff0c;存储空间在操作系统层面划分了三个逻辑分区。 数据库故障&#xff1a; 数据库文件丢失&#xff0c;主要涉及3个…

Adobe系列软件安装

双击解压 先运行Creative_Cloud_Set_Up.exe。 完毕后&#xff0c;运行AdobeGenP.exe 先Path&#xff0c;选路径&#xff0c;如 C:\Program Files\Adobe 后Search 最后Patch。 关闭软件&#xff0c;修图&#xff01;

【LeetCode刷题记录】124. 二叉树中的最大路径和

124 二叉树中的最大路径和 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的…