|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: 'Running SQLite in Pure Java with Quarkus' |
| 4 | +date: 2025-03-11T00:00:00Z |
| 5 | +tags: sqlite java wasm webassembly |
| 6 | +synopsis: 'Running SQLite in Pure Java with Quarkus' |
| 7 | +author: andreatp |
| 8 | +--- |
| 9 | +:imagesdir: /assets/images/posts/sqlite |
| 10 | + |
| 11 | +What if you could run a C-based database in pure Java, with zero configuration, and even compile it to a native image effortlessly? With the new Quarkiverse extension https://github.com/quarkiverse/quarkus-jdbc-sqlite4j[quarkus-jdbc-sqlite4j], you can do exactly that. |
| 12 | + |
| 13 | +Traditionally, embedded databases in Java require reimplementing their C counterparts, often leading to differences in behavior, missing optimizations, and delayed bug fixes. However, https://github.com/roastedroot/sqlite4j[sqlite4j] provides a JDBC driver that leverages the original SQLite C implementation while running safely inside a sandbox. |
| 14 | + |
| 15 | +== Hands-on Example |
| 16 | + |
| 17 | +To see https://github.com/quarkiverse/quarkus-jdbc-sqlite4j[quarkus-jdbc-sqlite4j] in action, you can start with any existing Quarkus application or one of the https://github.com/quarkusio/quarkus-quickstarts[quickstarts]. If you prefer a ready-made example, check out https://github.com/andreaTP/demo-hibernate-orm-quickstart-wasm[this demo repository], which integrates SQLite within a Quarkus application using Hibernate ORM. |
| 18 | + |
| 19 | +By simply changing the JDBC driver dependency, you can embed a fully functional SQLite database inside your application while retaining all the benefits of the native SQLite implementation. |
| 20 | + |
| 21 | +To get started, add the extension dependency to your _pom.xml_: |
| 22 | + |
| 23 | +[source,xml] |
| 24 | +---- |
| 25 | +<dependency> |
| 26 | + <groupId>io.quarkiverse.jdbc</groupId> |
| 27 | + <artifactId>quarkus-jdbc-sqlite4j</artifactId> |
| 28 | +</dependency> |
| 29 | +---- |
| 30 | + |
| 31 | +Then, configure your Quarkus application to use SQLite with standard JDBC settings: |
| 32 | + |
| 33 | +[source,properties] |
| 34 | +---- |
| 35 | +quarkus.datasource.db-kind=sqlite |
| 36 | +quarkus.datasource.jdbc.url=jdbc:sqlite:sample.db |
| 37 | +quarkus.datasource.jdbc.min-size=1 |
| 38 | +---- |
| 39 | + |
| 40 | +You can now use your datasource as you normally would with Hibernate and Panache. |
| 41 | +Note that we keep a minimum connection pool size > 0 to avoid redundant copies from disk to memory of the database. |
| 42 | + |
| 43 | +== Running in a Secure Sandbox |
| 44 | + |
| 45 | +Under the hood, SQLite runs in a fully in-memory sandboxed environment, ensuring security and isolation. |
| 46 | + |
| 47 | +image::vfs.png[width=80%, align="center", alt="Virtual FileSystem Usage"] |
| 48 | + |
| 49 | +When a connection to a local file is opened, the following occurs: |
| 50 | + |
| 51 | +. The database file is copied from disk to an in-memory Virtual FileSystem. |
| 52 | +. A connection is established to the in-memory database. |
| 53 | + |
| 54 | +While this approach is highly secure, many users need to persist database changes. One recommended solution is to periodically back up the in-memory database to disk. This can be achieved through a scheduled job that: |
| 55 | + |
| 56 | +. Backs up the in-memory database to a new file. |
| 57 | +. Copies the backup to the host file system. |
| 58 | +. Atomically replaces the old database file with the new backup. |
| 59 | + |
| 60 | +This setup ensures a seamless experience while maintaining SQLite's sandboxed security. You can adapt this approach to fit your specific needs. |
| 61 | + |
| 62 | +Here's a sample implementation: |
| 63 | + |
| 64 | +[source,java] |
| 65 | +---- |
| 66 | +@ApplicationScoped |
| 67 | +public class SQLiteBackup { |
| 68 | + @ConfigProperty(name = "quarkus.datasource.jdbc.url") |
| 69 | + String jdbcUrl; |
| 70 | +
|
| 71 | + @Inject |
| 72 | + AgroalDataSource dataSource; |
| 73 | +
|
| 74 | + // Execute a backup every 10 seconds |
| 75 | + @Scheduled(delay=10, delayUnit=TimeUnit.SECONDS, every="10s") |
| 76 | + void scheduled() { backup(); } |
| 77 | +
|
| 78 | + // Execute a backup during shutdown |
| 79 | + public void onShutdown(@Observes ShutdownEvent event) { backup(); } |
| 80 | +
|
| 81 | + void backup() { |
| 82 | + String dbFile = jdbcUrl.substring("jdbc:sqlite:".length()); |
| 83 | +
|
| 84 | + var originalDbFilePath = Paths.get(dbFile); |
| 85 | + var backupDbFilePath = originalDbFilePath |
| 86 | + .toAbsolutePath() |
| 87 | + .getParent() |
| 88 | + .resolve(originalDbFilePath.getFileName() + "_backup"); |
| 89 | +
|
| 90 | + try (var conn = dataSource.getConnection(); |
| 91 | + var stmt = conn.createStatement()) { |
| 92 | + // Execute the backup |
| 93 | + stmt.executeUpdate("backup to " + backupDbFilePath); |
| 94 | + // Atomically replace the DB file with its backup |
| 95 | + Files.move(backupDbFilePath, originalDbFilePath, |
| 96 | + StandardCopyOption.ATOMIC_MOVE, |
| 97 | + StandardCopyOption.REPLACE_EXISTING); |
| 98 | + } catch (IOException | SQLException e) { |
| 99 | + throw new RuntimeException("Failed to back up the database", e); |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | +---- |
| 104 | + |
| 105 | +== Technical Deep Dive |
| 106 | + |
| 107 | +https://github.com/roastedroot/sqlite4j[sqlite4j] compiles the official SQLite C https://www.sqlite.org/amalgamation.html[amalgamation] to WebAssembly (Wasm), which is then translated into Java bytecode using the https://chicory.dev/docs/experimental/aot[Chicory AOT compiler]. |
| 108 | +This enables SQLite to run in a pure Java environment while maintaining its full functionality. |
| 109 | + |
| 110 | +image::sqlite-compilation.png[width=80%, align="center", alt="SQLite compilation"] |
| 111 | + |
| 112 | +== Security and Isolation |
| 113 | + |
| 114 | +One of the key benefits of this approach is security. |
| 115 | +Running SQLite inside a Wasm sandbox ensures memory safety and isolates it from the host system, making it an excellent choice for applications that require embedded databases while avoiding the risks of native code execution. |
| 116 | + |
| 117 | +== Conclusion |
| 118 | + |
| 119 | +With the new https://github.com/quarkiverse/quarkus-jdbc-sqlite4j[quarkus-jdbc-sqlite4j] extension, you get the best of both worlds: the power and reliability of SQLite combined with the safety and portability of Java. |
| 120 | +This extension seamlessly integrates SQLite into Quarkus applications while maintaining a lightweight and secure architecture. Best of all, everything compiles effortlessly with __native-image__. |
| 121 | + |
| 122 | +Ready to try it out? Give https://github.com/quarkiverse/quarkus-jdbc-sqlite4j[quarkus-jdbc-sqlite4j] a spin in your projects and experience the benefits of running SQLite in pure Java with Quarkus! |
0 commit comments