我有一个用例需要备份数据库。与H2一样,数据库可以存储在一个文件中,这似乎很容易将文件复制出去。但是,这应该在应用程序运行时发生。
出于这个原因,我必须事先停止H2数据库,然后再重新启动。
我有这个基于Maven配置的简单Spring启动应用程序:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
<java.version>1.8</java.version>
<spring.boot.version>1.5.3.RELEASE</spring.boot.version>
</properties>
<artifactId>RestartH2</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这是应用程序:
package ch.sahits.game.h2;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class H2Application implements CommandLineRunner {
public static void main(String[] args) throws Exception {
SpringApplication.run(H2Application.class, args);
}
//access command line arguments
@Override
public void run(String... args) throws Exception {
// How to stop the H2 database
// backup the database file
// Restart the database
}
}
application.properties
:
spring.datasource.url=jdbc:h2:file:~/test;DB_CLOSE_ON_EXIT=FALSE
如果这不是基于文件的,我可以通过org.h2.tools.Server
,我可以启动并停止服务器,但解决方案是基于文件的。
为了停止数据库,我在AbstractEmbeddedDatabaseConfigurer
中找到了一些代码:
public void shutdown(DataSource dataSource, String databaseName) {
Connection con = null;
try {
con = dataSource.getConnection();
con.createStatement().execute("SHUTDOWN");
} catch (SQLException var13) {
this.logger.warn("Could not shut down embedded database", var13);
} finally {
if(con != null) {
try {
con.close();
} catch (Throwable var12) {
this.logger.debug("Could not close JDBC Connection on shutdown", var12);
}
}
}
}
然而,对于重新启动数据库,我不知道该怎么做。是否有可能,如果是的话怎么样?如果使用基于文件的方法无法做到这一点,那么在这种情况下如何实现数据库的备份呢?
更新:检查H2 documentation有一个执行备份的命令行工具,但数据库可能没有运行。还有一个在线解决方案,但目前尚不清楚如何恢复这样的备份。
答案 0 :(得分:0)
我找到了一个使用H2脚本工具创建可以恢复的SQL备份的解决方案:
public void run(String... args) throws Exception {
fleetH2Repository.deleteAll();
// Add fleet records
String playerUuid1 = UUID.randomUUID().toString();
String playerUuid2 = UUID.randomUUID().toString();
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 14), 3));
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 15), 3));
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 16), 3));
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 17), 4));
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 18), 4));
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 19), 4));
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 20), 3));
fleetH2Repository.save(new ShipH2Record(playerUuid1, LocalDate.of(1364,3, 21), 4));
fleetH2Repository.save(new ShipH2Record(playerUuid2, LocalDate.of(1364,3, 14), 13));
fleetH2Repository.save(new ShipH2Record(playerUuid2, LocalDate.of(1364,3, 15), 13));
fleetH2Repository.save(new ShipH2Record(playerUuid2, LocalDate.of(1364,3, 16), 13));
fleetH2Repository.save(new ShipH2Record(playerUuid2, LocalDate.of(1364,3, 17), 12));
fleetH2Repository.save(new ShipH2Record(playerUuid2, LocalDate.of(1364,3, 18), 12));
fleetH2Repository.save(new ShipH2Record(playerUuid2, LocalDate.of(1364,3, 19), 11));
fleetH2Repository.save(new ShipH2Record(playerUuid2, LocalDate.of(1364,3, 20), 11));
// Create a dump and add a statement to drop everything to make the restore work.
try {
String backupFile = "h2.backup.zip";
String tempOutputFilenName = "out.zip";
Script.main("-url", "jdbc:h2:file:~/.OpenPatrician/h2.db;DB_CLOSE_ON_EXIT=FALSE", "-user", "sa", "-script", tempOutputFilenName, "-options", "compression", "zip");
File f = new File(tempOutputFilenName);
ZipFile zipFile = new ZipFile(tempOutputFilenName);
final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(backupFile));
for(Enumeration e = zipFile.entries(); e.hasMoreElements(); ) {
ZipEntry entryIn = (ZipEntry) e.nextElement();
zos.putNextEntry(new ZipEntry(entryIn.getName()));
InputStream is = zipFile.getInputStream(entryIn);
byte[] firstBytes = "DROP ALL OBJECTS".getBytes();
zos.write(firstBytes);
byte[] buf = new byte[1024];
int len;
while ((len = (is.read(buf))) > 0) {
zos.write(buf, 0, (len < buf.length) ? len : buf.length);
}
zos.closeEntry();
}
zos.close();
f.delete();
} catch (SQLException |IOException e) {
e.printStackTrace();
}
// Restore
try {
RunScript.main("-url", "jdbc:h2:file:~/.OpenPatrician/h2.db;DB_CLOSE_ON_EXIT=FALSE", "-user", "sa", "-script", "h2.backup.zip", "-options", "compression", "zip");
} catch (SQLException e) {
e.printStackTrace();
}
}
棘手的部分是创建转储的脚本将为表添加create语句。但是,在运行脚本以还原数据时,表可能仍然存在,因此需要事先删除它们。这就是在脚本开头添加DROP ALL OBJECTS
的原因。
答案 1 :(得分:0)
如果您使用H2 tutorial中描述的BACKUP TO
语句简单备份数据库会怎样?
例如:
<强> ./ DB / backup.sql 强>
BACKUP TO './db/backup.zip'
备份方法
public void backupH2() {
try {
RunScript.execute("jdbc:h2:./db/data", "sa", "", "./db/backup.sql", Charset.defaultCharset(), true);
LOG.info("H2 is backed up.");
} catch (SQLException e) {
LOG.info("Cannot backup H2. Cause: {}", e.getMessage());
}
}
我的工作example。
答案 2 :(得分:0)
我的解决方案:
public class H2DatabaseBackup implements DatabaseBackup {
@Override // Backup database
public void backupDatabase(Connection conn, String file) throws SQLException {
PreparedStatement statement = conn.prepareStatement("SCRIPT TO ?");
statement.setString(1, file);
statement.execute();
}
@Override
public void resetDatabase(Connection conn, String file2) throws SQLException {
PreparedStatement preparedStatement =
conn.prepareStatement("DROP ALL OBJECTS;");
preparedStatement.executeUpdate();
PreparedStatement statement = conn.prepareStatement("RUNSCRIPT FROM ?");
statement.setString(1, file2);
statement.execute();
}
}
public interface DatabaseBackup {
public void backupDatabase(Connection conn, String file) throws SQLException;
public void importDatabase(Connection conn, String file1) throws SQLException;
public void resetDatabase(Connection conn, String file1) throws SQLException;
}