diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 3f879915b7db3..100a8f761eb7b 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -32,6 +32,7 @@
1.3.3
1.0.1
1.4.1
+ 3.5.5
1.1.0
1.8.4
2.2.3
@@ -1221,6 +1222,16 @@
quarkus-mongodb-client-deployment
${project.version}
+
+ io.quarkus
+ quarkus-mybatis
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-mybatis-deployment
+ ${project.version}
+
io.quarkus
quarkus-grpc
@@ -2877,6 +2888,11 @@
mongodb-crypt
${mongo-crypt.version}
+
+ org.mybatis
+ mybatis
+ ${mybatis.version}
+
net.minidev
json-smart
diff --git a/docs/src/main/asciidoc/mybatis.adoc b/docs/src/main/asciidoc/mybatis.adoc
new file mode 100644
index 0000000000000..f8e343022706a
--- /dev/null
+++ b/docs/src/main/asciidoc/mybatis.adoc
@@ -0,0 +1,274 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
+////
+= Quarkus - Using MyBatis
+include::./attributes.adoc[]
+:extension-status: preview
+
+This guide demonstrates how your Quarkus application can use link:https://mybatis.org/mybatis-3/[MyBatis] to support custom SQL, stored procedures and advanced mappings.
+
+include::./status-include.adoc[]
+
+== Prerequisites
+
+To complete this guide, you need:
+
+* less than 15 minutes
+* an IDE
+* JDK 1.8+ installed with `JAVA_HOME` configured appropriately
+* Apache Maven {maven-version}
+* A running Mysql Database server
+* GraalVM, or Docker, installed if you want to run in native mode.
+
+== Architecture
+
+The application built in this guide is quite simple: the user can get, add and remove a record through the RESTful API by using the MyBatis Mapper.
+
+
+== Solution
+
+We recommend that you follow the instructions in the next sections and create the application step by step.
+
+== Creating the Maven Project
+
+First, we need a new project. Create a new project with the following command:
+
+[source, subs=attributes+]
+----
+mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \
+ -DprojectGroupId=org.acme \
+ -DprojectArtifactId=mybatis-quickstart \
+ -Dextensions="mybatis,restesay-jackson,jdbc-mysql"
+cd mybatis-quickstart
+----
+This command generates a Maven project, with its pom.xml importing the quarkus-mybatis extension.
+
+If you already have your Quarkus project configured, you can add the `mybatis` extension
+to your project by running the following command in your project base directory:
+
+[source,bash]
+----
+./mvnw quarkus:add-extension -Dextensions="mybatis"
+----
+
+This will add the following to your `pom.xml`:
+
+[source]
+----
+
+ io.quarkus
+ quarkus-mybatis
+
+----
+
+== Creating the User POJO
+We are going to create a `User` POJO to access to the data in the backend mysql server.
+Create the `src/main/java/org/acme/mybatis/User.java` file, with the following content:
+
+[source, java]
+----
+package org.acme.mybatis;
+
+public class User {
+ private Integer id;
+ private String name;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+----
+
+== Creating the User Mapper
+We are going to create a `UserMapper` class which will use the MyBatis annotations to inject the SQL.
+Create the `src/main/java/org/acme/mybatis/UserMapper.java` file, with the following content:
+
+[source, java]
+----
+package org.acme.mybatis;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface UserMapper {
+
+ @Select("SELECT * FROM USERS WHERE id = #{id}")
+ User getUser(Integer id); // <1>
+
+ @Insert("INSERT INTO USERS (id, name) VALUES (#{id}, #{name})")
+ Integer createUser(@Param("id") Integer id, @Param("name") String name); // <2>
+
+ @Delete("DELETE FROM USERS WHERE id = #{id}")
+ Integer removeUser(Integer id); // <3>
+}
+----
+
+1. Get a user from the database.
+2. Insert a user into the database. We should use the `@Param` to bind the parameters.
+3. Delete a user from the databse.
+
+== Creating the MyBatisResource to handle the requests
+We are going to create a `MyBatisResource` class which will handle all the requests to create, query or remove the data
+from the database.
+
+[source, java]
+----
+package org.acme.mybatis;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Path("/mybatis")
+public class MyBatisResource {
+
+ @Inject
+ UserMapper userMapper; // <1>
+
+ @Path("/user/{id}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public User getUser(@PathParam("id") Integer id) {
+ return userMapper.getUser(id);
+ }
+
+ @Path("/user")
+ @POST
+ @Produces(MediaType.TEXT_PLAIN)
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Integer createUser(@FormParam("id") Integer id, @FormParam("name") String name) {
+ return userMapper.createUser(id, name);
+ }
+
+ @Path("/user/{id}")
+ @DELETE
+ @Produces(MediaType.TEXT_PLAIN)
+ public Integer removeUser(@PathParam("id") Integer id) {
+ return userMapper.removeUser(id);
+ }
+}
+----
+1. It uses the UserMapper which should be injected by the Quarkus to access the database.
+
+== Configure the properties
+
+We need to config the datasource used to connect to the database and the mybatis will choose the default one. Also you
+can use ```quarkus.mybatis.datasource``` for the specific database.
+
+[source]
+----
+quarkus.datasource.db-kind=mysql
+quarkus.datasource.username=
+
+quarkus.datasource.jdbc.url=jdbc:mysql://localhost/test #<1>
+quarkus.mybatis.initial-sql=insert.sql #<2>
+----
+
+1. The datasource used by the mybatis to connect the database.
+2. The SQL file which should be executed just after the application is started.
+
+We could keep the following content in the `insert.sql` to add some data:
+[source, sql]
+----
+DROP TABLE IF EXISTS USERS;
+
+CREATE TABLE USERS (
+ id integer not null primary key,
+ name varchar(80) not null
+);
+
+INSERT INTO USERS (id, name) values(1, 'Test User1');
+INSERT INTO USERS (id, name) values(2, 'Test User2');
+INSERT INTO USERS (id, name) values(3, 'Test User3');
+----
+
+== Running with the JVM mode
+At first, you should make sure the Mysql Server is running and the `test` database has been created.
+Then, you just need to run:
+
+[source, shell]
+----
+./mvnw compile quarkus:dev
+----
+
+You can get the user by using the following command:
+
+[source, shell]
+----
+curl http://localhost:8080/mybatis/user/1
+----
+
+Or create a new user:
+
+[source, shell]
+----
+curl -X POST http://localhost:8080/mybatis/user -d 'id=4&name=test'
+----
+
+Or remove a user:
+
+[source, shell]
+----
+curl -X DELETE http://localhost:8080/mybatis/user/1
+----
+
+== Running Native
+You have to add the `--report-unsupported-elements-at-runtime` option when buiding the native image now.
+So add the following content with the native profile in the `pom.xml`:
+
+[source, xml]
+----
+
+ native
+
+
+ --report-unsupported-elements-at-runtime
+
+
+
+----
+
+You can build the native executable with:
+
+[source, shell]
+----
+./mvnw package -Pnative
+----
+
+and then run with:
+
+[source, shell]
+----
+./target/mybatis-quickstart-1.0-SNAPSHOT-runner
+----
+
+== Using @CacheNamespace
+You have to set `readWrite=false` when building the native image since the `ObjectOutputStream.writeObject` has not been supported by Graal VM.
+
+== Configuration References
+include::{generated-dir}/config/quarkus-mybatis.adoc[opts=optional, leveloffset=+1]
diff --git a/extensions/mybatis/deployment/pom.xml b/extensions/mybatis/deployment/pom.xml
new file mode 100644
index 0000000000000..0ac31c599cde6
--- /dev/null
+++ b/extensions/mybatis/deployment/pom.xml
@@ -0,0 +1,64 @@
+
+
+
+ quarkus-mybatis-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-mybatis-deployment
+ Quarkus - Mybatis - Deployment
+
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-agroal-deployment
+
+
+ io.quarkus
+ quarkus-mybatis
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.quarkus
+ quarkus-test-h2
+ test
+
+
+ io.quarkus
+ quarkus-jdbc-h2-deployment
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
+
diff --git a/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MyBatisMapperBuildItem.java b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MyBatisMapperBuildItem.java
new file mode 100644
index 0000000000000..925d79c243aef
--- /dev/null
+++ b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MyBatisMapperBuildItem.java
@@ -0,0 +1,17 @@
+package io.quarkus.mybatis.deployment;
+
+import org.jboss.jandex.DotName;
+
+import io.quarkus.builder.item.MultiBuildItem;
+
+public final class MyBatisMapperBuildItem extends MultiBuildItem {
+ private final DotName mapperName;
+
+ public MyBatisMapperBuildItem(DotName mapperName) {
+ this.mapperName = mapperName;
+ }
+
+ public DotName getMapperName() {
+ return mapperName;
+ }
+}
diff --git a/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MybatisProcessor.java b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MybatisProcessor.java
new file mode 100644
index 0000000000000..db6d242ebe50d
--- /dev/null
+++ b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MybatisProcessor.java
@@ -0,0 +1,175 @@
+package io.quarkus.mybatis.deployment;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.inject.Singleton;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.cache.decorators.LruCache;
+import org.apache.ibatis.cache.impl.PerpetualCache;
+import org.apache.ibatis.javassist.util.proxy.ProxyFactory;
+import org.apache.ibatis.logging.log4j.Log4jImpl;
+import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
+import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
+import org.jboss.jandex.DotName;
+import org.jboss.logging.Logger;
+
+import io.quarkus.agroal.deployment.JdbcDataSourceBuildItem;
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.arc.deployment.BeanContainerBuildItem;
+import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
+import io.quarkus.deployment.configuration.ConfigurationError;
+import io.quarkus.mybatis.runtime.MyBatisProducers;
+import io.quarkus.mybatis.runtime.MyBatisRecorder;
+import io.quarkus.mybatis.runtime.MyBatisRuntimeConfig;
+
+class MybatisProcessor {
+
+ private static final Logger LOG = Logger.getLogger(MybatisProcessor.class);
+ private static final String FEATURE = "mybatis";
+ private static final DotName MYBATIS_MAPPER = DotName.createSimple(Mapper.class.getName());
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem(FEATURE);
+ }
+
+ @BuildStep
+ void runtimeInitialzed(BuildProducer runtimeInit) {
+ runtimeInit.produce(new RuntimeInitializedClassBuildItem(Log4jImpl.class.getName()));
+ }
+
+ @BuildStep
+ void reflectiveClasses(BuildProducer reflectiveClass) {
+ reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
+ ProxyFactory.class,
+ XMLLanguageDriver.class,
+ RawLanguageDriver.class));
+
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, true,
+ PerpetualCache.class, LruCache.class));
+ }
+
+ @BuildStep
+ void addMyBatisMappers(BuildProducer mappers,
+ BuildProducer reflective,
+ BuildProducer proxy,
+ CombinedIndexBuildItem indexBuildItem) {
+ for (AnnotationInstance i : indexBuildItem.getIndex().getAnnotations(MYBATIS_MAPPER)) {
+ if (i.target().kind() == AnnotationTarget.Kind.CLASS) {
+ DotName dotName = i.target().asClass().name();
+ mappers.produce(new MyBatisMapperBuildItem(dotName));
+ reflective.produce(new ReflectiveClassBuildItem(true, false, dotName.toString()));
+ proxy.produce(new NativeImageProxyDefinitionBuildItem(dotName.toString()));
+ }
+ }
+ }
+
+ @BuildStep
+ void unremovableBeans(BuildProducer beanProducer) {
+ beanProducer.produce(AdditionalBeanBuildItem.unremovableOf(MyBatisProducers.class));
+ }
+
+ @BuildStep
+ void initalSql(BuildProducer resource, MyBatisRuntimeConfig config) {
+ if (config.initialSql.isPresent()) {
+ resource.produce(new NativeImageResourceBuildItem(config.initialSql.get()));
+ }
+ }
+
+ @Record(ExecutionTime.RUNTIME_INIT)
+ @BuildStep
+ SqlSessionFactoryBuildItem generateSqlSessionFactory(MyBatisRuntimeConfig myBatisRuntimeConfig,
+ List myBatisMapperBuildItems,
+ List jdbcDataSourcesBuildItem,
+ MyBatisRecorder recorder) {
+ List mappers = myBatisMapperBuildItems
+ .stream().map(m -> m.getMapperName().toString()).collect(Collectors.toList());
+
+ String dataSourceName;
+ if (myBatisRuntimeConfig.dataSource.isPresent()) {
+ dataSourceName = myBatisRuntimeConfig.dataSource.get();
+ Optional jdbcDataSourceBuildItem = jdbcDataSourcesBuildItem.stream()
+ .filter(i -> i.getName().equals(dataSourceName))
+ .findFirst();
+ if (!jdbcDataSourceBuildItem.isPresent()) {
+ throw new ConfigurationError("Can not find datasource " + dataSourceName);
+ }
+ } else {
+ Optional defaultJdbcDataSourceBuildItem = jdbcDataSourcesBuildItem.stream()
+ .filter(i -> i.isDefault())
+ .findFirst();
+ if (defaultJdbcDataSourceBuildItem.isPresent()) {
+ dataSourceName = defaultJdbcDataSourceBuildItem.get().getName();
+ } else {
+ throw new ConfigurationError("No default datasource");
+ }
+ }
+
+ return new SqlSessionFactoryBuildItem(recorder.createSqlSessionFactory(
+ myBatisRuntimeConfig.environment,
+ myBatisRuntimeConfig.transactionFactory,
+ dataSourceName,
+ mappers));
+ }
+
+ @Record(ExecutionTime.RUNTIME_INIT)
+ @BuildStep
+ SqlSessionManagerBuildItem generateSqlSessionManager(SqlSessionFactoryBuildItem sqlSessionFactoryBuildItem,
+ MyBatisRecorder recorder) {
+ return new SqlSessionManagerBuildItem(recorder.createSqlSessionManager(
+ sqlSessionFactoryBuildItem.getSqlSessionFactory()));
+ }
+
+ @Record(ExecutionTime.RUNTIME_INIT)
+ @BuildStep
+ void generateMapperBeans(MyBatisRecorder recorder,
+ List myBatisMapperBuildItems,
+ SqlSessionManagerBuildItem sqlSessionManagerBuildItem,
+ BuildProducer syntheticBeanBuildItemBuildProducer) {
+
+ for (MyBatisMapperBuildItem i : myBatisMapperBuildItems) {
+ SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
+ .configure(i.getMapperName())
+ .scope(Singleton.class)
+ .setRuntimeInit()
+ .unremovable()
+ .supplier(recorder.MyBatisMapperSupplier(i.getMapperName().toString(),
+ sqlSessionManagerBuildItem.getSqlSessionManager()));
+ syntheticBeanBuildItemBuildProducer.produce(configurator.done());
+ }
+ }
+
+ @Record(ExecutionTime.RUNTIME_INIT)
+ @BuildStep
+ void register(SqlSessionFactoryBuildItem sqlSessionFactoryBuildItem,
+ BeanContainerBuildItem beanContainerBuildItem,
+ MyBatisRecorder recorder) {
+ recorder.register(sqlSessionFactoryBuildItem.getSqlSessionFactory(), beanContainerBuildItem.getValue());
+ }
+
+ @Record(ExecutionTime.RUNTIME_INIT)
+ @BuildStep
+ void runInitialSql(SqlSessionFactoryBuildItem sqlSessionFactoryBuildItem,
+ MyBatisRuntimeConfig myBatisRuntimeConfig,
+ MyBatisRecorder recorder) {
+ if (myBatisRuntimeConfig.initialSql.isPresent()) {
+ recorder.runInitialSql(sqlSessionFactoryBuildItem.getSqlSessionFactory(),
+ myBatisRuntimeConfig.initialSql.get());
+ }
+ }
+}
diff --git a/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionFactoryBuildItem.java b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionFactoryBuildItem.java
new file mode 100644
index 0000000000000..38decde6b4085
--- /dev/null
+++ b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionFactoryBuildItem.java
@@ -0,0 +1,21 @@
+package io.quarkus.mybatis.deployment;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.runtime.RuntimeValue;
+
+/**
+ * Hold the RuntimeValue of {@link SqlSessionFactory}
+ */
+public final class SqlSessionFactoryBuildItem extends SimpleBuildItem {
+ private final RuntimeValue sqlSessionFactory;
+
+ public SqlSessionFactoryBuildItem(RuntimeValue sqlSessionFactory) {
+ this.sqlSessionFactory = sqlSessionFactory;
+ }
+
+ public RuntimeValue getSqlSessionFactory() {
+ return sqlSessionFactory;
+ }
+}
diff --git a/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionManagerBuildItem.java b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionManagerBuildItem.java
new file mode 100644
index 0000000000000..2b19d7d674ad2
--- /dev/null
+++ b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionManagerBuildItem.java
@@ -0,0 +1,21 @@
+package io.quarkus.mybatis.deployment;
+
+import org.apache.ibatis.session.SqlSessionManager;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+import io.quarkus.runtime.RuntimeValue;
+
+/**
+ * Hold the RuntimeValue of {@link SqlSessionManager}
+ */
+public final class SqlSessionManagerBuildItem extends SimpleBuildItem {
+ private final RuntimeValue sqlSessionManager;
+
+ public SqlSessionManagerBuildItem(RuntimeValue sqlSessionManager) {
+ this.sqlSessionManager = sqlSessionManager;
+ }
+
+ public RuntimeValue getSqlSessionManager() {
+ return sqlSessionManager;
+ }
+}
diff --git a/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/MyBatisTest.java b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/MyBatisTest.java
new file mode 100644
index 0000000000000..77d9f1724949b
--- /dev/null
+++ b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/MyBatisTest.java
@@ -0,0 +1,34 @@
+package io.quarkus.mybatis.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.inject.Inject;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class MyBatisTest {
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest().withConfigurationResource("application.properties")
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(UserMapper.class, User.class));
+
+ @Inject
+ UserMapper userMapper;
+
+ @Inject
+ SqlSessionFactory sqlSessionFactory;
+
+ @Test
+ public void test() throws Exception {
+ assertTrue(sqlSessionFactory.getConfiguration().getMapperRegistry().hasMapper(UserMapper.class));
+ User user = userMapper.getUser(1);
+ assertEquals(user.getId(), 1);
+ assertEquals(user.getName(), "Test User1");
+ }
+}
diff --git a/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/TestResources.java b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/TestResources.java
new file mode 100644
index 0000000000000..463ef736ab47a
--- /dev/null
+++ b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/TestResources.java
@@ -0,0 +1,8 @@
+package io.quarkus.mybatis.test;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.h2.H2DatabaseTestResource;
+
+@QuarkusTestResource(H2DatabaseTestResource.class)
+public class TestResources {
+}
diff --git a/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/User.java b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/User.java
new file mode 100644
index 0000000000000..22a2703b93b43
--- /dev/null
+++ b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/User.java
@@ -0,0 +1,22 @@
+package io.quarkus.mybatis.test;
+
+public class User {
+ private Integer id;
+ private String name;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/UserMapper.java b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/UserMapper.java
new file mode 100644
index 0000000000000..f0c2bc6fceeb6
--- /dev/null
+++ b/extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/UserMapper.java
@@ -0,0 +1,11 @@
+package io.quarkus.mybatis.test;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+@Mapper
+public interface UserMapper {
+
+ @Select("select * from users where id = #{id}")
+ User getUser(Integer id);
+}
diff --git a/extensions/mybatis/deployment/src/test/resources/application.properties b/extensions/mybatis/deployment/src/test/resources/application.properties
new file mode 100644
index 0000000000000..cfdfa74d72918
--- /dev/null
+++ b/extensions/mybatis/deployment/src/test/resources/application.properties
@@ -0,0 +1,5 @@
+quarkus.datasource.db-kind=h2
+quarkus.datasource.username=username-default
+
+quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:default
+quarkus.mybatis.initial-sql=insert.sql
diff --git a/extensions/mybatis/deployment/src/test/resources/insert.sql b/extensions/mybatis/deployment/src/test/resources/insert.sql
new file mode 100644
index 0000000000000..a6edad4e71398
--- /dev/null
+++ b/extensions/mybatis/deployment/src/test/resources/insert.sql
@@ -0,0 +1,9 @@
+CREATE TABLE USERS (
+ id integer not null primary key,
+ name varchar(80) not null
+);
+
+DELETE FROM users;
+insert into users (id, name) values(1, 'Test User1');
+insert into users (id, name) values(2, 'Test User2');
+insert into users (id, name) values(3, 'Test User3');
diff --git a/extensions/mybatis/pom.xml b/extensions/mybatis/pom.xml
new file mode 100644
index 0000000000000..0d46a93bc5256
--- /dev/null
+++ b/extensions/mybatis/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ quarkus-build-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../../build-parent/pom.xml
+
+ 4.0.0
+
+ quarkus-mybatis-parent
+ Quarkus - Mybatis
+ pom
+
+
+ deployment
+ runtime
+
+
diff --git a/extensions/mybatis/runtime/pom.xml b/extensions/mybatis/runtime/pom.xml
new file mode 100644
index 0000000000000..ee55066abf1e3
--- /dev/null
+++ b/extensions/mybatis/runtime/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+ quarkus-mybatis-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-mybatis
+ Quarkus - Mybatis - Runtime
+ MyBatis SQL mapper framework for Java
+
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-agroal
+
+
+ org.mybatis
+ mybatis
+
+
+ org.graalvm.nativeimage
+ svm
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisProducers.java b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisProducers.java
new file mode 100644
index 0000000000000..a3c95ae529ca9
--- /dev/null
+++ b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisProducers.java
@@ -0,0 +1,21 @@
+package io.quarkus.mybatis.runtime;
+
+import javax.enterprise.inject.Produces;
+import javax.inject.Singleton;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+
+@Singleton
+public class MyBatisProducers {
+ private volatile SqlSessionFactory sqlSessionFactory;
+
+ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
+ this.sqlSessionFactory = sqlSessionFactory;
+ }
+
+ @Singleton
+ @Produces
+ SqlSessionFactory sqlSessionFactory() {
+ return this.sqlSessionFactory;
+ }
+}
diff --git a/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRecorder.java b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRecorder.java
new file mode 100644
index 0000000000000..8080ab38c5a3f
--- /dev/null
+++ b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRecorder.java
@@ -0,0 +1,88 @@
+package io.quarkus.mybatis.runtime;
+
+import java.io.Reader;
+import java.sql.Connection;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.jdbc.ScriptRunner;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
+import org.apache.ibatis.session.SqlSessionManager;
+import org.apache.ibatis.transaction.TransactionFactory;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
+import org.jboss.logging.Logger;
+
+import io.quarkus.agroal.runtime.DataSources;
+import io.quarkus.arc.runtime.BeanContainer;
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.annotations.Recorder;
+
+@Recorder
+public class MyBatisRecorder {
+ private static final Logger LOG = Logger.getLogger(MyBatisRecorder.class);
+
+ public RuntimeValue createSqlSessionFactory(
+ String environment, String transactionFactory, String dataSourceName, List mappers) {
+ Configuration configuration = new Configuration();
+
+ TransactionFactory factory;
+ if (transactionFactory.equals("MANAGED")) {
+ factory = new ManagedTransactionFactory();
+ } else {
+ factory = new JdbcTransactionFactory();
+ }
+
+ Environment.Builder environmentBuilder = new Environment.Builder(environment).transactionFactory(factory).dataSource(
+ DataSources.fromName(dataSourceName));
+
+ configuration.setEnvironment(environmentBuilder.build());
+ for (String mapper : mappers) {
+ try {
+ configuration.addMapper(Resources.classForName(mapper));
+ } catch (ClassNotFoundException e) {
+ LOG.debug("Can not find the mapper class " + mapper);
+ }
+ }
+
+ SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
+ return new RuntimeValue<>(sqlSessionFactory);
+ }
+
+ public RuntimeValue createSqlSessionManager(RuntimeValue sqlSessionFactory) {
+ SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(sqlSessionFactory.getValue());
+ return new RuntimeValue<>(sqlSessionManager);
+ }
+
+ public Supplier