目录
前言
笔者之前学习 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
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 的旧项目时还遇到过的两点小问题
- 旧项目中代码生成器(mybatis-plus-generator)的相关依赖和的相关代码也在迁移时被删掉了,旧的代码生成器(3.4.3.1)似乎是不兼容 JDK17
- 如果旧项目中允许实体类的部分字段以 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 那一套方法来,因此也不算是什么上策