使用 Groovy™ 和 GraalVM 处理 SQL 数据库

作者: Paul King

发布时间:2022-07-29 02:07PM


本周,关于最新的 GraalVM 22.2 版本,有一个有趣的视频博客文章。该版本包含许多新功能和改进,其中包括:

  • 更小的原生可执行文件

  • 在原生可执行文件中生成堆转储的能力

  • IntelliJ IDEA 中实验性的原生镜像调试

  • (使用 GraalVM Enterprise 时)能够将软件物料清单 (SBOM) 嵌入到可执行文件中,以提高安全性

  • 原生元数据集成

本博客将关注其中最后一点。我们将使用视频中讨论的 H2 数据库作为运行示例。

原生元数据

对于任何使用过 GraalVM 的人来说,他们都知道通常必须向原生编译器提供某些信息。某些类可以在构建时初始化,而其他类应在运行时初始化。如果访问某些类型的资源,则必须将这些资源的知识提供给编译器。应用程序中可能通过反射或涉及序列化调用的部分可能不被视为可到达,并且不会被编译器自动包含。

应用程序中使用的每个库都将拥有自己的一组类和资源,通常需要使用该库的任何人来处理。原生元数据存储库以每个库为基础保留此信息的共享副本。一旦有人填充了元数据,使用相同库的其他项目就可以自动获取该信息。我们稍后会更详细地了解元数据集成,但首先,让我们看看我们的数据库应用程序。

在 Groovy 中使用 SQL

该应用程序创建并填充了一个包含四个客户的 customer 数据库。然后它将它们打印出来

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 的 Sql 类使这相对容易。withInstance 方法将创建数据库连接并在完成后关闭它。executeInsert 方法使用 Groovy 内插字符串 (GString),它在内部创建预处理语句。

配置我们的原生构建

这是我们的构建文件

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',
            )
        }
    }
}

我们使用了 graalvm 原生构建插件。我们定义了 Groovy 和 H2 的依赖项。我们还可以向原生编译器提供任何必要的参数。重要的是,我们启用了与元数据存储库的集成。

当我们运行构建时,它会自动为我们创建原生应用程序

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]

检查原生镜像速度

原生镜像构建完成后,我们还可以检查速度

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

更多信息

从存储库查看完整的源代码
https://github.com/paulk-asert/groovy-graalvm-h2

结论

我们已经研究了一个简单的 H2 数据库应用程序以及使用 Groovy 和 GraalVM 创建原生应用程序所涉及的步骤。