データベースへアクセスする際のライブラリとしてMyBatisを使用しているSpring Bootアプリケーションを、Nativeイメージ化して動かす方法を紹介したいと思います。この投稿では、非常にシンプルなデータアクセスを行うサンプルアプリケーションを、GraalVMを使ってNativeイメージ化して実行してみます。
利用する主なプロダクト・ライブラリ
- GraalVM 22.0.0.2 (OpenJDK 17)
- spring-boot 2.6.3
- mybatis-spring-boot 2.2.2 (mybatis 3.5.9 and mybatis-spring 2.0.7)
- spring-native 0.11.2
- mybatis-spring-native 0.1.0-SNAPSHOT
IMPORTANT:
spring-nativeとmybatis-spring-nativeはGAバージョンではないので、今後のバージョンアップにより本投稿に記載している内容で動かなくなる可能性はある点を補足しておきます。また、spring-natvieは最終的にはSpring Framework 6に組み込まれてリリースされる予定になっています。
spring-nativeって何者?
spring-nativeは、Spring(Spring Boot)で作成したアプリケーションをGraalVMを使ってNativeイメージ化して動かすために必要な仕組みやツールなどを提供してくれているライブラリです。詳しくはリファレンスページをご覧ください。
mybatis-spring-nativeって何者?
mybatis-spring-nativeは、spring-nativeの仕組みを利用して「MyBatisから提供しているライブラリ」をNativeイメージ化して動かすために必要な設定や機能を提供するライブラリです。
NOTE:
投稿時点では、以下のライブラリがサポート対象になっています。
- mybatis
- mybatis-spring
- mybatis-spring-boot
- mybatis-thymelaf
- mybatis-freemarker
- mybatis-velocity
- mybatis-dynamic-sql
動かしてみよう!!
Quick Startをベースに実際にNativeイメージ化してアプリケーションを動かしてみましょう。
GraalVMのインストール
以下のページを参考に、各自の環境に合わせてGraalVMをインストールしてください。
なお、GraalVMをインストールしたら、以下の環境変数を設定してください。
JAVA_HOME
GRAALVM_HOME
本投稿では、Docker Containerを使う方法で記載します。
docker run -v $(pwd):/work -v $HOME/.m2:/root/.m2 -it -w /work \
-e JAVA_HOME=/opt/graalvm-ce-java17-22.0.0.2 \
-e GRAALVM_HOME=/opt/graalvm-ce-java17-22.0.0.2 \
-e LANG=C.utf8 \
ghcr.io/graalvm/graalvm-ce:22.0.0.2 bash
NOTE:
Dockerコンテナを使う場合は、Mavenビルド中に「'Error: Image build request failed with exit status 137'」というエラーが発生するかもしれません。これはNativeイメージ作成中にメモリ不足が発生した時に出るエラーであり、Dockerに割り当てるメモリを増やすことで解決することができます。
プロジェクト作成
SPRING INITIALIZR経由でサンプルアプリケーション開発用のプロジェクトを作成します。
curl -s https://start.spring.io/starter.tgz\
-d name=mybatis-sample\
-d artifactId=mybatis-sample\
-d dependencies=mybatis,h2,native\
-d baseDir=mybatis-sample\
| tar -xzvf - && cd mybatis-sample
mybatis-sample/
mybatis-sample/mvnw.cmd
mybatis-sample/.gitignore
mybatis-sample/src/
mybatis-sample/src/main/
mybatis-sample/src/main/java/
mybatis-sample/src/main/java/com/
mybatis-sample/src/main/java/com/example/
mybatis-sample/src/main/java/com/example/mybatissample/
mybatis-sample/src/main/java/com/example/mybatissample/MybatisSampleApplication.java
mybatis-sample/src/main/resources/
mybatis-sample/src/main/resources/application.properties
mybatis-sample/src/test/
mybatis-sample/src/test/java/
mybatis-sample/src/test/java/com/
mybatis-sample/src/test/java/com/example/
mybatis-sample/src/test/java/com/example/mybatissample/
mybatis-sample/src/test/java/com/example/mybatissample/MybatisSampleApplicationTests.java
mybatis-sample/.mvn/
mybatis-sample/.mvn/wrapper/
mybatis-sample/.mvn/wrapper/maven-wrapper.properties
mybatis-sample/.mvn/wrapper/maven-wrapper.jar
mybatis-sample/pom.xml
mybatis-sample/HELP.md
mybatis-sample/mvnw
mybatis-spring-nativeを追加
SPRING INITIALIZR経由で作成したプロジェクトにはmybatis-spring-nativeは含まれていないので、pom.xml
に追加する必要があります。
<dependency>
<groupId>org.mybatis.spring.native</groupId>
<artifactId>mybatis-spring-native-core</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
また、mybatis-spring-nativeはまだSNAPSHOTバージョンなので、リポジトリ設定にSonatype OSSのSNAPSHOTリポジトリを追加してください。
<repository>
<id>sonatype-oss-snapshots</id>
<name>Sonatype OSS Snapshots Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
Nativeイメージをビルドしてみる
まだサンプル実装を追加していませんが、Nativeイメージが作成できるか確認してみましょう!!
./mvnw package -Pnative -DskipTests
Nativeイメージを実行してみる
./target/mybatis-sample
2022-02-05 07:36:51.798 INFO 568 --- [ main] o.s.nativex.NativeListener : AOT mode enabled
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.3)
2022-02-05 07:36:51.800 INFO 568 --- [ main] c.e.m.MybatisSampleApplication : Starting MybatisSampleApplication v0.0.1-SNAPSHOT using Java 17.0.2 on bba9728656ca with PID 568 (/work/mybatis-sample/target/mybatis-sample started by root in /work/mybatis-sample)
2022-02-05 07:36:51.800 INFO 568 --- [ main] c.e.m.MybatisSampleApplication : No active profile set, falling back to default profiles: default
2022-02-05 07:36:51.813 INFO 568 --- [ main] c.e.m.MybatisSampleApplication : Started MybatisSampleApplication in 0.044 seconds (JVM running for 0.046)
起動時間を見てみると・・・「0.046秒」であることがわかります。早いっすね。どれくらい起動時間が早いかを比較するために、実行可能jarをjavaコマンドを使用して実行してみましょう。
java -jar ./target/mybatis-sample-0.0.1-SNAPSHOT-exec.jar
2022-02-05 07:39:43.931 INFO 640 --- [ main] o.s.nativex.NativeListener : AOT mode disabled
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v0.0.1-SNAPSHOT)
2022-02-05 07:39:43.985 INFO 640 --- [ main] c.e.m.MybatisSampleApplication : Starting MybatisSampleApplication v0.0.1-SNAPSHOT using Java 17.0.2 on bba9728656ca with PID 640 (/work/mybatis-sample/target/mybatis-sample-0.0.1-SNAPSHOT-exec.jar started by root in /work/mybatis-sample)
2022-02-05 07:39:43.985 INFO 640 --- [ main] c.e.m.MybatisSampleApplication : No active profile set, falling back to default profiles: default
2022-02-05 07:39:44.607 WARN 640 --- [ main] o.m.s.mapper.ClassPathMapperScanner : No MyBatis mapper was found in '[com.example.mybatissample]' package. Please check your configuration.
2022-02-05 07:39:44.925 INFO 640 --- [ main] c.e.m.MybatisSampleApplication : Started MybatisSampleApplication in 1.27 seconds (JVM running for 1.632)
起動時間を見てみると・・・「1.632秒」なので、Nativeイメージの方が圧倒的に速いことがわかります!! (そのかわりビルドには時間かかりますけどね・・・ )
SQLファイルの作成
src/main/resources/schema.sql
に以下のSQLを記載して保存する。
CREATE TABLE city
(
id INT PRIMARY KEY auto_increment,
name VARCHAR,
state VARCHAR,
country VARCHAR
);
ドメインクラスの作成
src/main/java/com/example/mybatissample/City.java
に以下のコードを記載して保存する。
package com.example.mybatissample;
public class City {
private Long id;
private String name;
private String state;
private String country;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return this.country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return getId() + "," + getName() + "," + getState() + "," + getCountry();
}
}
Mapperインターフェースの作成
src/main/java/com/example/mybatissample/CityMapper.java
に以下のコードを記載して保存する。
package com.example.mybatissample;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface CityMapper {
@Insert("INSERT INTO city (name, state, country) VALUES(#{name}, #{state}, #{country})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(City city);
@Select("SELECT id, name, state, country FROM city WHERE id = #{id}")
City findById(long id);
}
アプリケーションクラスの修正
src/main/java/com/example/mybatissample/MybatisSampleApplication.java
を以下のコードに置き換えて保存する。
package com.example.mybatissample;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MybatisSampleApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisSampleApplication.class, args);
}
private final CityMapper cityMapper;
public MybatisSampleApplication(CityMapper cityMapper) {
this.cityMapper = cityMapper;
}
@Bean
CommandLineRunner sampleCommandLineRunner() {
return args -> {
City city = new City();
city.setName("San Francisco");
city.setState("CA");
city.setCountry("US");
cityMapper.insert(city);
System.out.println(this.cityMapper.findById(city.getId()));
};
}
}
Nativeイメージのビルド&実行
./mvnw package -Pnative -DskipTests
./target/mybatis-sample
2022-02-05 07:56:55.364 INFO 1163 --- [ main] o.s.nativex.NativeListener : AOT mode enabled
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.3)
2022-02-05 07:56:55.366 INFO 1163 --- [ main] c.e.m.MybatisSampleApplication : Starting MybatisSampleApplication v0.0.1-SNAPSHOT using Java 17.0.2 on bba9728656ca with PID 1163 (/work/mybatis-sample/target/mybatis-sample started by root in /work/mybatis-sample)
2022-02-05 07:56:55.366 INFO 1163 --- [ main] c.e.m.MybatisSampleApplication : No active profile set, falling back to default profiles: default
2022-02-05 07:56:55.381 INFO 1163 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-02-05 07:56:55.384 INFO 1163 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-02-05 07:56:55.387 INFO 1163 --- [ main] c.e.m.MybatisSampleApplication : Started MybatisSampleApplication in 0.058 seconds (JVM running for 0.06)
1,San Francisco,CA,US
2022-02-05 07:56:55.388 INFO 1163 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-02-05 07:56:55.389 INFO 1163 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
java -jar ./target/mybatis-sample-0.0.1-SNAPSHOT-exec.jar
2022-02-05 08:00:59.837 INFO 1255 --- [ main] o.s.nativex.NativeListener : AOT mode disabled
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.3)
2022-02-05 08:00:59.896 INFO 1255 --- [ main] c.e.m.MybatisSampleApplication : Starting MybatisSampleApplication v0.0.1-SNAPSHOT using Java 17.0.2 on bba9728656ca with PID 1255 (/work/mybatis-sample/target/mybatis-sample-0.0.1-SNAPSHOT-exec.jar started by root in /work/mybatis-sample)
2022-02-05 08:00:59.897 INFO 1255 --- [ main] c.e.m.MybatisSampleApplication : No active profile set, falling back to default profiles: default
2022-02-05 08:01:00.861 INFO 1255 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-02-05 08:01:01.019 INFO 1255 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-02-05 08:01:01.104 INFO 1255 --- [ main] c.e.m.MybatisSampleApplication : Started MybatisSampleApplication in 1.552 seconds (JVM running for 1.908)
1,San Francisco,CA,US
2022-02-05 08:01:01.149 INFO 1255 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-02-05 08:01:01.152 INFO 1255 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
DBアクセス処理の実行時間は、Nativeイメージが「0.001秒」、実行可能jarが「0.045秒」なので、Nativeイメージの方が圧倒的に速いことがわかります!!
まとめ
基本的にはアプリケーションのコードを変更することなく、Nativeイメージ化できました。
ただし・・・実際のアプリケーションでは、spring-nativeがサポートしていないライブラリを使っている(使う)ことも多いと思うので、それらのライブラリをNativeイメージ化するための設定が必要になる可能性は高いかな〜と思いますが、これはspring-nativeの機能を自身のアプリケーション内で使うことで解決することができます。是非、spring-nativeを使ったNativeイメージ化にチャレンジしてみてください!!