Working with SQL databases with Groovy and GraalVM
During the week, there was an interesting video and blog post on the latest GraalVM 22.2 Release. The release has numerous new features and improvements including:
- smaller native executables
- the ability to generate heap dumps in native executables
- experimental native image debugging within IntelliJ IDEA
- the ability to embed a Software Bill of Materials (SBOM) into the executable for improved security (when using GraalVM Enterprise)
- native metadata integration.
This blog looks at the last of these. We'll use the running example of the H2 database which the video discusses.
Native Metadata
For anyone who has used GraalVM, they will know that frequently certain information must be given to the native compiler. Certain classes can be initialized at build time, others should be initialized at runtime. If accessing certain kinds of resources, knowledge of those resources must be given to the compiler. Parts of the application which might be invoked through reflection or involve serialization, might not be deemed reachable and won't automatically be included by the compiler.
Each library that is being used within an application will have its own set of classes and resources which will commonly need to dealt with by anyone using that library. The Native Metadata repository keeps a shared copy of this information on a per library basis. Once someone has populated the metadata, other projects using the same library can get that information automatically. We'll look more at metadata integration shortly, but first, let's look at our database application.
Working with SQL in Groovy
The application creates and then populates a customer database with four customers. It then prints them out:
import groovy.sql.Sql
import groovy.transform.CompileStatic
@CompileStatic
class H2Demo {
static void main(args) {
Sql.withInstance('jdbc:h2:./data/test') { sql ->
sql.execute 'DROP TABLE IF EXISTS customers'
sql.execute 'CREATE TABLE customers(id INTEGER AUTO_INCREMENT, name VARCHAR)'
for (cust in ['Lord Archimonde', 'Arthur', 'Gilbert', 'Grug']) {
sql.executeInsert "INSERT INTO customers(name) VALUES $cust"
}
println sql.rows('SELECT * FROM customers').join('\n')
}
}
}
Groovy's Sql
class makes this relatively easy. The withInstance
method will create a database connection and close it down when finished with. The executeInsert
method is using a Groovy interpolated String (GString) which creates a prepared statement under the covers.
Configuring our native build
Here is our build file:
plugins {
id 'application'
id 'groovy'
id 'org.graalvm.buildtools.native'
}
application {
mainClass = 'H2Demo'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.h2database:h2:2.1.210'
implementation 'org.apache.groovy:groovy:4.0.4'
implementation 'org.apache.groovy:groovy-sql:4.0.4'
}
graalvmNative {
agent {
defaultMode = 'standard'
}
metadataRepository {
enabled = true
}
binaries {
main {
buildArgs.addAll(
// '-H:IncludeSBOM=cyclonedx',
'--report-unsupported-elements-at-runtime',
'--initialize-at-run-time=groovy.grape.GrapeIvy,org.h2.store.fs.niomem.FileNioMemData',
'--initialize-at-build-time',
'--no-fallback',
)
}
}
}
We make use of the graalvm native build plugin. We define our dependencies of Groovy and H2. We can also supply any needed parameters to the native compiler. Importantly, we enable integration with the metadata repository.
When we run the build, it will automatically create the native app for us:
paulk@pop-os:/extra/projects/groovy-graalvm-h2$ ./gradlew clean nativeRun ... > Task :nativeCompile [native-image-plugin] Using executable path: /extra/devtools/graalvm-ce-java17-22.2.0/bin/native-image ======================================================================================================================== GraalVM Native Image: Generating 'H2Demo' (executable)... ======================================================================================================================== ... [1/7] Initializing... (5.3s @ 0.26GB) Version info: 'GraalVM 22.2.0 Java 17 CE' Java version info: '17.0.4+8-jvmci-22.2-b06' C compiler: gcc (linux, x86_64, 11.2.0) Garbage collector: Serial GC 1 user-specific feature(s) - com.oracle.svm.polyglot.groovy.GroovyIndyInterfaceFeature [2/7] Performing analysis... [************] (51.7s @ 1.82GB) 10,597 (90.60%) of 11,697 classes reachable 17,002 (64.13%) of 26,510 fields reachable 58,165 (63.45%) of 91,666 methods reachable 393 classes, 100 fields, and 2,057 methods registered for reflection 65 classes, 74 fields, and 55 methods registered for JNI access 4 native libraries: dl, pthread, rt, z [3/7] Building universe... (8.0s @ 4.02GB) [4/7] Parsing methods... [**] (4.8s @ 3.85GB) [5/7] Inlining methods... [***] (3.0s @ 1.72GB) [6/7] Compiling methods... [******] (38.0s @ 3.63GB) [7/7] Creating image... (5.9s @ 1.70GB) 26.65MB (46.64%) for code area: 38,890 compilation units 28.04MB (49.05%) for image heap: 359,812 objects and 66 resources 2.46MB ( 4.31%) for other data 57.15MB in total ------------------------------------------------------------------------------------------------------------------------ Top 10 packages in code area: Top 10 object types in image heap: 1.48MB sun.security.ssl 5.85MB byte[] for code metadata 1.06MB java.util 2.82MB java.lang.String 979.43KB java.lang.invoke 2.78MB java.lang.Class 758.29KB org.apache.groovy.parser.antlr4 2.47MB byte[] for general heap data 723.92KB com.sun.crypto.provider 2.04MB byte[] for java.lang.String 588.57KB org.h2.table 910.68KB com.oracle.svm.core.hub.DynamicHubCompanion 582.06KB org.h2.command 764.95KB java.util.HashMap$Node 494.23KB org.codehaus.groovy.classgen 761.53KB java.lang.Object[] 476.03KB c.s.org.apache.xerces.internal.impl.xs.traversers 715.65KB byte[] for embedded resources 468.69KB java.lang 584.75KB java.util.HashMap$Node[] 18.87MB for 370 more packages 8.28MB for 2535 more object types ------------------------------------------------------------------------------------------------------------------------ 3.9s (3.2% of total time) in 30 GCs | Peak RSS: 6.22GB | CPU load: 6.48 ------------------------------------------------------------------------------------------------------------------------ Produced artifacts: /extra/projects/groovy-graalvm-h2/build/native/nativeCompile/H2Demo (executable) /extra/projects/groovy-graalvm-h2/build/native/nativeCompile/H2Demo.build_artifacts.txt (txt) ======================================================================================================================== Finished generating 'H2Demo' in 2m 1s. [native-image-plugin] Native Image written to: /extra/projects/groovy-graalvm-h2/build/native/nativeCompile > Task :nativeRun [ID:1, NAME:Lord Archimonde] [ID:2, NAME:Arthur] [ID:3, NAME:Gilbert] [ID:4, NAME:Grug]
Checking the native image speed
We can also check the speed once the native image is built:
paulk@pop-os:/extra/projects/groovy-graalvm-h2$ time build/native/nativeCompile/H2Demo [ID:1, NAME:Lord Archimonde] [ID:2, NAME:Arthur] [ID:3, NAME:Gilbert] [ID:4, NAME:Grug] real 0m0.027s user 0m0.010s sys 0m0.011s
More information
Check out the full source code from the repo: https://github.com/paulk-asert/groovy-graalvm-h2.
Conclusion
We have looked at a simple H2 database application and the steps involved in creating a native application with Groovy and GraalVM.