从Spring应用程序停止并重新启动基于文件的H2数据库

时间:2017-05-19 20:25:18

标签: java spring spring-boot spring-data-jpa h2

我有一个用例需要备份数据库。与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有一个执行备份的命令行工具,但数据库可能没有运行。还有一个在线解决方案,但目前尚不清楚如何恢复这样的备份。

3 个答案:

答案 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;


}