【折腾GraalVM二】GraalVM下Web后端框架与数据库DAO层框架的技术选型(2023-09)

前言

笔者之前学习 Java 时主要接触的是 Spring + MyBatis 框架,因此这里的技术选型必然不算完整,仅为个人经验总结

注意,以下的内容并不算是手把手的教程,可能会省略部分过程,笔者默认读者有基本的 Maven 构建经验 和 基本的GraalVM使用经验

2023-09-15记:这里先把目录搭出来,之后每个技术组合应该都会附单独的demo工程
另外,如果文章中还存在本句话,证明这篇文章还没彻底写完,写完后本句话会被删掉

Quarkus + MyBatis-Plus

2023 年的上半年我没找到 SpringBoot 3 + MyBatis Plus 的相关成功实践,当时比较成熟的技术选型是 Quarkus + MyBatis-Plus,因此先介绍这个

可以直接去 Quarkus 的 MyBatis-Plus 扩展页面点击 TRY THIS EXTENSION 按钮

https://quarkus.io/extensions/io.quarkiverse.mybatis/quarkus-mybatis-plus

在这里插入图片描述
之后会跳到这个页面,基本类似 start.spring.io,改好包名,再选上一些必要的依赖(比如数据库驱动之类的),最后 Generate 就行了
在这里插入图片描述
这样下载得到的 Quarkus 项目应该没有主类和 main 方法,启动程序使用的是这条 Maven 命令

./mvnw compile quarkus:dev

打包编译 exe 则使用的是 -Dnative install 这条命令,并不是 package

./mvnw -Dnative install

另外值得注意的是,这个 Quarkus MyBatis-Plus 插件默认是无法进行 Java 风格的驼峰命名与数据库风格的下划线命名 之间的转换的(比如实体类中有一个叫 pageNum 的属性,那默认是不能和数据库中的 page_num 字段对应上的,这点和 MyBatis-Plus 在 SpringBoot 中的表现的应该不太一样)

所以如果需要 实体类驼峰命名映射数据库下划线命名

  • 要么是手动给 Java 实体类的各属性加上 @TableField() 注解,逐个手动映射

  • 要么是在 application.properties 里面加上这一条,手动在 MyBatis 层面启用 下划线转驼峰式 的特性

    quarkus.mybatis.map-underscore-to-camel-case=true
    

    此配置项参考了以下文档,文档中的 map-underscore-to-camel-case 配置项默认是false

    http://docs.quarkiverse.io/quarkus-mybatis/dev/index.html

SpringBoot 3 + MyBatis-Plus(基于GitHub中的示例)

这个技术方案目前基本上参照下面 GitHub 上的这个 Demo 工程就可以了,详见 Issue #5527

GitHub - nieqiurong/mybatis-native-demo
Very simple demo application for native image using MyBatis Spring Boot Starter 3.x
https://github.com/nieqiurong/mybatis-native-demo

不过需要注意的是,如果是在官网下载的 JDK20 版本的 GraalVM 环境下(java --version 输出结果如下)

java 20.0.2 2023-07-18
Java(TM) SE Runtime Environment Oracle GraalVM 20.0.2+9.1 (build 20.0.2+9-jvmci-23.0-b14)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 20.0.2+9.1 (build 20.0.2+9-jvmci-23.0-b14, mixed mode, sharing)

想要 native-image 编译这个 Demo 为 exe,则需要在 pom.xml 的 build > plugins > plugin > org.graalvm.buildtools > configuration 的 buildArgs 里面加入 -H:+AllowDeprecatedBuilderClassesOnImageClasspath 这个参数,完整的 build 部分如下

  <build>
    <plugins>
      <plugin>
        <groupId>org.graalvm.buildtools</groupId>
        <artifactId>native-maven-plugin</artifactId>
        <configuration>
          <buildArgs combine.children="append">
            <buildArg>--enable-url-protocols=http</buildArg>
            <buildArg>--features=com.example.nativedemo.LambdaRegistrationFeature</buildArg>
            <!-- 如果是 jdk20及以上 的 graalvm 就需要下面这一行 -->
            <buildArg>-H:+AllowDeprecatedBuilderClassesOnImageClasspath</buildArg>
          </buildArgs>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

否则会报错说

Error: Class-path entry org/graalvm/sdk/graal-sdk/23.0.1/graal-sdk-23.0.1.jar contains class com.oracle.svm.core.annotate.TargetElement. This class is part of the image builder itself (in jrt:/org.graalvm.sdk) and must not be passed via -cp. This can be caused by a fat-jar that illegally includes svm.jar (or graal-sdk.jar) due to its build-time dependency on it. As a workaround, -H:+AllowDeprecatedBuilderClassesOnImageClasspath allows turning this error into a warning. Note that this option is deprecated and will be removed in a future version.

这个问题的具体原因还在探索,不过按照报错说的加入上述的 jvm 参数确实就能编译了,只不过又多了很多 warning

另外需要注意的是,按照这个 Demo 迁移旧项目的时候除了需要对比拷贝 pom.xml 之外,还需要拷贝 \src\main\java\com\example\nativedemo\ 中的三个配置类,个人理解的这三个类的功能如下

  • LambdaRegistrationFeature.java
    使 lambda 表达式功能注入到 Graal 中
  • MyBatisNativeConfiguration.java
    使 MyBatis 和 MyBatis Plus 相关类注入到 Graal 中
  • MybatisPlusConfig.java
    实现插件的注册,比如分页插件

之前迁移旧项目的时候忘记了拷贝这三个类,结果正常 java 运行 jar 包没问题,但 native-image 打包完 exe 之后运行就报错,原因就是忘记拷贝了它们

以下是我迁移一个 JDK 8,SpringBoot 2.5.2 ,Mybatis-Plus 3.4.3.1 的旧项目时还遇到过的两点小问题

  1. 旧项目中代码生成器(mybatis-plus-generator)的相关依赖和的相关代码也在迁移时被删掉了,旧的代码生成器(3.4.3.1)似乎是不兼容 JDK17
  2. 如果旧项目中允许实体类的部分字段以 null 值插入数据库,根据 Issue #5129 FieldStrategy 的 IGNORED 命名建议 ,这些实体类字段中出现的 FieldStrategy.IGNORED 写法最好被替换为 FieldStrategy.ALWAYS (否则会有个 Deprecated 警告),不过这个问题和 GraalVM 没啥关系,只是升级了 MyBatis Plus 版本导致的

SpringBoot 3 + JPA

不得不承认,Spring 生态下的组件用起来确实是没什么问题的,虽然这我是我第一次用 JPA,但是从运行到打包 exe 都没出过什么问题,除了打包 exe 时有些慢之外

SpringBoot 3 + JdbcTemplate + 自己写 RowMapper

这里看似和下面使用 BeanPropertyRowMapper 自动生成的架构差不多,其实编译打包 exe 的流程则有所不同。简而言之,如果是自己手写每次查询结果的 RowMapper,则直接打包编译 exe 即可。如果是使用了 BeanPropertyRowMapper 这类基于反射特性的工具,则需要先使用 agentlib 运行 jar 包生成反射相关的配置文件,并加入 java 反射后门参数,之后再进行 native-image 编译

SpringBoot 3 + JdbcTemplate + BeanPropertyRowMapper 自动生成

比较喜欢写原生 SQL 的话,可以大道至简,就用 JdbcTemplate 进行数据库查询,也是种不错的选择,mapper可以自己写,也可以 BeanPropertyRowMapper 自动生成,但是自动生成是有代价的,如果是使用了 BeanPropertyRowMapper,则不能简单地直接进行 native-image 编译打包了,需要先运行 agentlib 生成反射相关的配置文件,并加入 java 反射后门参数,之后再进行 native-image 编译

需要注意的是,Controller层写RestController时,需要写好返回值的类型,我一开始图方便,直接写了个 public Object test() ,然后事实上返回了一个 List<Book> ,这个写法在Java jar运行的时候没问题,但是打包exe之后运行,在返回到接口处序列化时会报一个
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class demo.halozhy.springboot3jdbctemplatetest.Book and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS). This appears to be a native image, in which case you may need to configure reflection for the class that is to be serialized (through reference chain: java.util.ArrayList[0]) 的异常,意思是说Book类没办法序列化了,其实并不是没办法序列化,而是返回值写的不对,应该写成 public List<Book> test() ,该返回什么就写什么,不要图省事写个Object

SpringBoot 3 + Anima(sql2o)

与 SpringBoot 3 + JdbcTemplate + BeanPropertyRowMapper 自动生成 的思路一致

后记(碎碎念)

截止到写这篇文章的2023年9月,SpringBoot 3 已经原生支持 Spring Native了,因此可以很好地支持 native-image 打包编译 exe,不过 DAO 层(数据访问层)的框架就总显得慢了一拍,个人之前在用的 MyBatis-Plus 框架在2023年上半年对 Spring Native 的支持一直没什么大的动静,到了2023年8月份终于在 GitHub 上面 Pin 了一个 Issue #5527,里面给出了一个示例工程,可以跑通 native-image 了,可喜可贺

奈何今年上半年要做毕业设计,当时就想在论文里面水一水 GraalVM 的特性,因此尝试了当时就已经可用的 Quarkus + MyBatis-Plus 的技术选型,这个方案算是成熟的,并且 Quarkus 通过 quarkus-spring-web 和 quarkus-spring-di 两个依赖支持了SpringBoot 的部分常见注解,因此从 SpringBoot 过渡到 Quarkus 可以说是基本平稳的

早有听闻 Spring Data JPA 对 Spring Native 的支持比较好,因此如果是 JPA 习惯者的话,可以试试 SpringBoot + Spring Data JPA 的组合

如果打算以手写 SQL 语句为主,使用比较原生的 JdbcTemplate 也是不错的选择,但是需要自己写数据库表到 Java 对象的 Mapper,如果不想自己写 Mapper 的话,使用 BeanPropertyRowMapper 也是可以的,不过在 JDK17 及以上的版本里,想用这些涉及到反射的东西,都需要带着 --add-opens java.base/java.lang=ALL-UNNAMED 参数运行,又想编译 exe,就又要用 native-image-agent 进行代理运行,之后带着生成的配置文件进行 native-image 打包,感觉也蛮繁琐的(我也在想,如果哪天 Java 把 --add-opens 这个后门也给禁了,那使用反射的一众类库岂不都要一命呜呼?)

另一个比较小众的 DAO 层框架是一个名为 Anima 的库,这个库用起来比较简单轻量,不过底层是依赖一个叫 sql2o 的库,这个 sql2o 对 JDK17 的支持也不是很好,运行打包编译 exe 还是需要按照上述的 JdbcTemplate 那一套方法来,因此也不算是什么上策

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值