在springboot应用中使用commons-exec备份MYSQL数据库

MYSQL本身提供了一个可执行程序 mysqldump 。它可以完成数据库的备份。

简单来说就是一个命令:

mysqldump -u[用户名] -p[密码]  [数据库]  > [备份的SQL文件]

# 注意 > 符号在linux下是重定向符,把标准输出重定向到文件
# 例如,备份demo库到c:/mysql.sql
mysqldump -uroot -proot demo  > c:/mysql.sql	

mysqldump的详细文档: MySQL :: MySQL 8.0 Reference Manual :: 4.5.4 mysqldump — A Database Backup Program

commons-exec

备份是通过新启动一个子进程完成。建议使用commons-exec库,比较简单。它设计得比较合理,包含了子进程超时控制,异步执行等等功能。

Maven 坐标:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-exec</artifactId>
    <version>1.3</version>
</dependency>

备份demo

package io.springboot.test;



import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;
import org.junit.Test;
import org.junit.runner.RunWith;
import io.springboot.DemoApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;

import com.zaxxer.hikari.HikariDataSource;

import lombok.extern.slf4j.Slf4j;


/**
 * 
 * 测试
 * @author KevinBlandy
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@Slf4j
public class DataBaseBackUpTest {
	
	@Autowired
	private HikariDataSource hikariDataSource;
	
	@Test
	public void test () throws Exception {
		
		// 备份的SQL文件
		Path sqlFile = Paths.get("C:\\Users\\KevinBlandy\\Desktop\\db.sql");
		
		String database = null;

		// 执行SQL获取当前数据库的名称
		try (Connection connection = hikariDataSource.getConnection()) {
			try (ResultSet resultSet = connection.createStatement().executeQuery("SELECT DATABASE();")) {
				if (resultSet.next()) {
					database = resultSet.getString(1);
				}
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}

		// 异常输出
		ByteArrayOutputStream stdErr = new ByteArrayOutputStream();
		
		// 标准输出。标准流输出的内容就是SQL的备份内容
		OutputStream stdOut = new BufferedOutputStream(Files.newOutputStream(sqlFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
		
		try (stdErr; stdOut) {

			// 监视狗。执行超时时间,1小时
			ExecuteWatchdog watchdog = new ExecuteWatchdog(TimeUnit.HOURS.toMillis(1));
			
			// 子进程执行器
			DefaultExecutor defaultExecutor = new DefaultExecutor();
			// defaultExecutor.setWorkingDirectory(null);  // 工作目录
			defaultExecutor.setWatchdog(watchdog);
			defaultExecutor.setStreamHandler(new PumpStreamHandler(stdOut, stdErr));		

			// 进程执行命令
			CommandLine commandLine = new CommandLine("mysqldump");
			commandLine.addArgument("-u" + hikariDataSource.getUsername()); // 用户名
			commandLine.addArgument("-p" + hikariDataSource.getPassword()); // 密码
			commandLine.addArgument(database); // 数据库

			log.info("导出SQL数据...");
			
			// 同步执行,阻塞直到子进程执行完毕。
			int exitCode = defaultExecutor.execute(commandLine);
			
			if(defaultExecutor.isFailure(exitCode) && watchdog.killedProcess()) {
				log.error("备份超时");
			}
			
			log.info("SQL数据导出完毕: exitCode={}, sqlFile={}", exitCode, sqlFile.toString());

		} catch (Exception e) {
			log.error("SQL数据导出异常: {}", e.getMessage());
			log.error("std err: {}{}", System.lineSeparator(), stdErr.toString());
		}
	}
}


1 Like

余哥总结的是真不赖

1 Like