Skip to content

Commit 011f5dd

Browse files
andreaTPmetacosm
authored andcommitted
implement scheduled backup for the SQLite DB
1 parent ca0547e commit 011f5dd

File tree

4 files changed

+71
-25
lines changed

4 files changed

+71
-25
lines changed

server/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
<groupId>io.quarkus</groupId>
2727
<artifactId>quarkus-hibernate-orm-panache</artifactId>
2828
</dependency>
29+
<dependency>
30+
<groupId>io.quarkus</groupId>
31+
<artifactId>quarkus-scheduler</artifactId>
32+
</dependency>
2933
<dependency>
3034
<groupId>io.quarkiverse.jdbc</groupId>
3135
<artifactId>quarkus-jdbc-sqlite4j</artifactId>

server/power-server.db

12 KB
Binary file not shown.
Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,84 @@
11
package net.laprun.sustainability.power.persistence;
22

3-
import java.sql.Connection;
4-
import java.sql.DriverManager;
3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.nio.file.Paths;
6+
import java.nio.file.StandardCopyOption;
57
import java.sql.SQLException;
6-
import java.sql.Statement;
8+
import java.util.concurrent.TimeUnit;
9+
import java.util.concurrent.atomic.AtomicBoolean;
710

11+
import jakarta.enterprise.context.ApplicationScoped;
812
import jakarta.enterprise.event.Observes;
13+
import jakarta.inject.Inject;
14+
import jakarta.persistence.EntityManager;
915

1016
import org.eclipse.microprofile.config.inject.ConfigProperty;
17+
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
18+
import org.hibernate.internal.SessionImpl;
19+
import org.jboss.logging.Logger;
1120

12-
import io.quarkus.logging.Log;
1321
import io.quarkus.runtime.ShutdownEvent;
22+
import io.quarkus.scheduler.Scheduled;
1423

24+
@ApplicationScoped
1525
public class SQLiteFilePersister {
1626
@ConfigProperty(name = "quarkus.datasource.jdbc.url")
1727
String jdbcUrl;
1828

19-
@ConfigProperty(name = "power-server.db.location")
20-
String fileName;
29+
private static final Logger LOGGER = Logger.getLogger(SQLiteFilePersister.class.getName());
2130

22-
// public void onStartup(@Observes StartupEvent event) {
23-
// Log.info("Restoring data from " + fileName);
24-
// try (
25-
// Connection connection = DriverManager.getConnection(jdbcUrl);
26-
// Statement statement = connection.createStatement()) {
27-
// statement.executeUpdate("restore from " + fileName);
28-
// } catch (SQLException e) {
29-
// e.printStackTrace(System.err);
30-
// }
31-
// }
31+
@Inject
32+
EntityManager entityManager;
3233

34+
private final AtomicBoolean executing = new AtomicBoolean(false);
35+
36+
// Execute a backup every 10 seconds
37+
@Scheduled(delay = 10, delayUnit = TimeUnit.SECONDS, every = "10s")
38+
void scheduled() {
39+
backup();
40+
}
41+
42+
// Execute a backup during shutdown
3343
public void onShutdown(@Observes ShutdownEvent event) {
34-
Log.info("Saving data to " + fileName);
35-
try (
36-
Connection connection = DriverManager.getConnection(jdbcUrl);
37-
Statement statement = connection.createStatement()) {
38-
statement.executeUpdate("backup to " + fileName);
39-
} catch (SQLException e) {
40-
e.printStackTrace(System.err);
44+
backup();
45+
}
46+
47+
void backup() {
48+
if (executing.compareAndSet(false, true)) {
49+
try {
50+
int prefixLength = "jdbc:sqlite:".length();
51+
int queryParamsIdx = jdbcUrl.indexOf('?');
52+
int length = (queryParamsIdx != -1) ? queryParamsIdx : jdbcUrl.length();
53+
String dbFile = jdbcUrl.substring(prefixLength, length);
54+
55+
var originalDbFilePath = Paths.get(dbFile);
56+
LOGGER.info("Starting DB backup for file: " + dbFile);
57+
var backupDbFilePath = originalDbFilePath.toAbsolutePath().getParent()
58+
.resolve(originalDbFilePath.getFileName() + "_backup");
59+
60+
JdbcConnectionAccess access = entityManager
61+
.unwrap(SessionImpl.class)
62+
.getJdbcConnectionAccess();
63+
try (var conn = access.obtainConnection();
64+
var stmt = conn.createStatement()) {
65+
// Execute the backup
66+
stmt.executeUpdate("backup to " + backupDbFilePath);
67+
// Atomically substitute the DB file with its backup
68+
Files.move(backupDbFilePath, originalDbFilePath, StandardCopyOption.ATOMIC_MOVE,
69+
StandardCopyOption.REPLACE_EXISTING);
70+
} catch (SQLException e) {
71+
throw new RuntimeException("Failed to backup the database", e);
72+
} catch (IOException e) {
73+
throw new RuntimeException("Failed to create backup files or folders", e);
74+
}
75+
LOGGER.info("Backup of " + dbFile + " completed.");
76+
} finally {
77+
executing.set(false);
78+
}
79+
} else {
80+
LOGGER.info("Backup in progress.");
4181
}
4282
}
83+
4384
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
quarkus.http.port=20432
22
quarkus.datasource.jdbc.url=jdbc:sqlite:power-server.db
33
quarkus.datasource.db-kind=sqlite
4+
quarkus.datasource.jdbc.min-size=1
45

5-
quarkus.hibernate-orm.database.generation=create
6-
power-server.db.location=${PWD}/power-server.sqlite
6+
# Only use this property once to create the initial database
7+
# quarkus.hibernate-orm.database.generation=create

0 commit comments

Comments
 (0)