Skip to content

Quick Start for building native image

Kazuki Shimizu edited this page May 20, 2023 · 8 revisions

Let's create a MyBatis Spring Boot Application with quickly using the SPRING INITIALIZR.

Pre conditions

You need to install GraalVM and need to following environment variables are defined.

  • JAVA_HOME
  • GRAALVM_HOME

Create a project

Create a Spring Boot standalone application for MyBatis + H2 Database using following command (or the SPRING INITIALIZR UI).

curl -s https://start.spring.io/starter.tgz\
     -d type=maven-project\
     -d name=mybatis-native-sample\
     -d artifactId=mybatis-native-sample\
     -d dependencies=mybatis,h2\
     -d baseDir=mybatis-native-sample\
     | tar -xzvf - && cd mybatis-native-sample

Build on Docker container

You can use the Docker container for building as follow:

NOTE:

If occurred the 'Error: Image build request failed with exit status 137', please increase the memory size that assign to Docker.

Run the container.

docker run -v $(pwd):/work -v $HOME/.m2:/root/.m2 -it -w /work \
  -e JAVA_HOME=/opt/graalvm-ce-java17-22.3.2 \
  -e GRAALVM_HOME=/opt/graalvm-ce-java17-22.3.2 \
  -e LANG=C.utf8 \
  ghcr.io/graalvm/graalvm-ce:22.3.2 bash

Try to build a native image for empty application.

./mvnw -Pnative clean native:compile

Run a native image.

./target/mybatis-native-sample

Unfortunately you get the following error:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.6)

2023-05-08T23:30:12.810Z  INFO 363 --- [           main] c.e.m.MybatisNativeSampleApplication     : Starting AOT-processed MybatisNativeSampleApplication using Java 17.0.7 with PID 363 (/work/target/mybatis-native-sample started by root in /work)
2023-05-08T23:30:12.812Z  INFO 363 --- [           main] c.e.m.MybatisNativeSampleApplication     : No active profile set, falling back to 1 default profile: "default"
2023-05-08T23:30:12.819Z ERROR 363 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.ExceptionInInitializerError: null
	at org.mybatis.spring.mapper.MapperScannerConfigurer.postProcessBeanDefinitionRegistry(MapperScannerConfigurer.java:363) ~[mybatis-native-sample:3.0.0]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344) ~[na:na]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:145) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:747) ~[mybatis-native-sample:6.0.8]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:565) ~[mybatis-native-sample:6.0.8]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[mybatis-native-sample:3.0.6]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[mybatis-native-sample:3.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[mybatis-native-sample:3.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) ~[mybatis-native-sample:3.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) ~[mybatis-native-sample:3.0.6]
	at com.example.mybatisnativesample.MybatisNativeSampleApplication.main(MybatisNativeSampleApplication.java:10) ~[mybatis-native-sample:na]
Caused by: org.apache.ibatis.logging.LogException: Error creating logger for logger org.mybatis.spring.mapper.ClassPathMapperScanner.  Cause: java.lang.NullPointerException
	at org.apache.ibatis.logging.LogFactory.getLog(LogFactory.java:54) ~[na:na]
	at org.apache.ibatis.logging.LogFactory.getLog(LogFactory.java:47) ~[na:na]
	at org.mybatis.logging.LoggerFactory.getLogger(LoggerFactory.java:32) ~[na:na]
	at org.mybatis.spring.mapper.ClassPathMapperScanner.<clinit>(ClassPathMapperScanner.java:60) ~[na:na]
	... 11 common frames omitted
Caused by: java.lang.NullPointerException: null
	at org.apache.ibatis.logging.LogFactory.getLog(LogFactory.java:52) ~[na:na]
	... 14 common frames omitted

Create a configuration class

Create the MyBatisNativeConfiguration class(src/main/com/example/mybatisnativesample/MyBatisNativeConfiguration.java) for building a native image and rebuild a native image.

./mvnw -Pnative clean native:compile

Run a native image.

./target/mybatis-native-sample

You can run a native image successfully as follow:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.6)

2023-05-08T23:50:38.069Z  INFO 667 --- [           main] c.e.m.MybatisNativeSampleApplication     : Starting AOT-processed MybatisNativeSampleApplication using Java 17.0.7 with PID 667 (/work/target/mybatis-native-sample started by root in /work)
2023-05-08T23:50:38.069Z  INFO 667 --- [           main] c.e.m.MybatisNativeSampleApplication     : No active profile set, falling back to 1 default profile: "default"
2023-05-08T23:50:38.088Z  INFO 667 --- [           main] c.e.m.MybatisNativeSampleApplication     : Started MybatisNativeSampleApplication in 0.053 seconds (process running for 0.058)

Create sql files

Create an sql file(src/main/resources/schema.sql) to generate the city table.

CREATE TABLE city
(
  id      INT PRIMARY KEY auto_increment,
  name    VARCHAR,
  state   VARCHAR,
  country VARCHAR
);

Create a domain class

Create the City class(src/main/java/com/example/mybatisnativesample/City.java).

package com.example.mybatisnativesample;

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();
  }

}

Create a mapper interface

Create the CityMapper interface(src/main/java/com/example/mybatisnativesample/CityMapper.java) for annotation driven.

package com.example.mybatisnativesample;

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);

}

Modify a spring boot application class

Add a bean definition that implements the CommandLineRunner interface at the MybatisSampleApplication class(src/main/java/com/example/mybatisnativesample/MybatisNativeSampleApplication.java) and call a mapper method.

package com.example.mybatisnativesample;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication(proxyBeanMethods = false)
public class MybatisNativeSampleApplication {

  public static void main(String[] args) {
    SpringApplication.run(MybatisNativeSampleApplication.class, args);
  }

  private final CityMapper cityMapper;

  public MybatisNativeSampleApplication(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()));
    };
  }

}

Run a spring boot application

Using spring-boot:run

At first, run an application using the Spring Boot Maven Plugin.

./mvnw spring-boot:run
`  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.6)

2023-05-09T00:04:36.356Z  INFO 924 --- [           main] c.e.m.MybatisNativeSampleApplication     : Starting MybatisNativeSampleApplication using Java 17.0.7 with PID 924 (/work/target/classes started by root in /work)
2023-05-09T00:04:36.360Z  INFO 924 --- [           main] c.e.m.MybatisNativeSampleApplication     : No active profile set, falling back to 1 default profile: "default"
2023-05-09T00:04:37.282Z  INFO 924 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-05-09T00:04:37.614Z  INFO 924 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:cb5ee681-55e3-4766-aa1b-693dd1c2c304 user=SA
2023-05-09T00:04:37.617Z  INFO 924 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-05-09T00:04:37.895Z  INFO 924 --- [           main] c.e.m.MybatisNativeSampleApplication     : Started MybatisNativeSampleApplication in 2.101 seconds (process running for 2.453)
1,San Francisco,CA,US
2023-05-09T00:04:37.968Z  INFO 924 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-05-09T00:04:37.971Z  INFO 924 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
...

Using native-image and executable jar

Also, you can package to a native-image and an executable jar file as follow:

./mvnw -Pnative clean native:compile

Run with native-image as follow:

./target/mybatis-native-sample
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.6)

2023-05-09T00:09:16.431Z  INFO 1250 --- [           main] c.e.m.MybatisNativeSampleApplication     : Starting AOT-processed MybatisNativeSampleApplication using Java 17.0.7 with PID 1250 (/work/target/mybatis-native-sample started by root in /work)
2023-05-09T00:09:16.431Z  INFO 1250 --- [           main] c.e.m.MybatisNativeSampleApplication     : No active profile set, falling back to 1 default profile: "default"
2023-05-09T00:09:16.439Z  INFO 1250 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-05-09T00:09:16.442Z  INFO 1250 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:0fe4e08f-8762-42f9-861a-e7ba0d917edb user=SA
2023-05-09T00:09:16.442Z  INFO 1250 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-05-09T00:09:16.456Z  INFO 1250 --- [           main] c.e.m.MybatisNativeSampleApplication     : Started MybatisNativeSampleApplication in 0.061 seconds (process running for 0.068)
1,San Francisco,CA,US
2023-05-09T00:09:16.458Z  INFO 1250 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-05-09T00:09:16.458Z  INFO 1250 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Run with executable jar as follow:

java -jar target/mybatis-native-sample-0.0.1-SNAPSHOT.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.6)

2023-05-09T00:10:27.878Z  INFO 1297 --- [           main] c.e.m.MybatisNativeSampleApplication     : Starting MybatisNativeSampleApplication v0.0.1-SNAPSHOT using Java 17.0.7 with PID 1297 (/work/target/mybatis-native-sample-0.0.1-SNAPSHOT.jar started by root in /work)
2023-05-09T00:10:27.881Z  INFO 1297 --- [           main] c.e.m.MybatisNativeSampleApplication     : No active profile set, falling back to 1 default profile: "default"
2023-05-09T00:10:28.448Z  INFO 1297 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-05-09T00:10:28.617Z  INFO 1297 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:351fc39e-6cfd-4d0c-95d2-92859b1133f0 user=SA
2023-05-09T00:10:28.620Z  INFO 1297 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-05-09T00:10:28.840Z  INFO 1297 --- [           main] c.e.m.MybatisNativeSampleApplication     : Started MybatisNativeSampleApplication in 1.299 seconds (process running for 1.681)
1,San Francisco,CA,US
2023-05-09T00:10:28.887Z  INFO 1297 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-05-09T00:10:28.890Z  INFO 1297 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Appendix

How to use XML based Mapper

If you want to write SQLs using XML file, you should modify the native-maven-plugin's configuration in pom.xml as follow:

<plugin>
  <groupId>org.graalvm.buildtools</groupId>
  <artifactId>native-maven-plugin</artifactId>
  <configuration>
    <buildArgs combine.children="append">
      <buildArg>--enable-url-protocols=http</buildArg> <!-- ★★★Need to specify★★★ -->
    </buildArgs>
  </configuration>
</plugin>

And you need to run the native image with -Djavax.xml.accessExternalDTD=all as follow:

./target/mybatis-native-sample -Djavax.xml.accessExternalDTD=all

How to use @MapperScan

If you want to scan mappers using @MapperScan, you should be specified either the sqlSessionTemplateRef or sqlSessionFactoryRef as follows:

@MapperScan(basePackages = "com.example.mybatisnativesample.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
@SpringBootApplication
public class MybatisNativeSampleApplication {
 // ...
}