From e01114a904fa1ba0e5f3d4cec30baa73f52f3a1e Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Thu, 9 Jul 2020 15:14:16 +0800 Subject: [PATCH 1/8] Fix #6490 add mybatis extension --- bom/application/pom.xml | 16 ++ extensions/mybatis/deployment/pom.xml | 69 +++++++++ .../deployment/MyBatisMapperBuildItem.java | 17 +++ .../mybatis/deployment/MybatisProcessor.java | 140 ++++++++++++++++++ .../SqlSessionFactoryBuildItem.java | 21 +++ .../SqlSessionManagerBuildItem.java | 21 +++ .../io/quarkus/mybatis/test/MyBatisTest.java | 34 +++++ .../quarkus/mybatis/test/TestResources.java | 8 + .../java/io/quarkus/mybatis/test/User.java | 22 +++ .../io/quarkus/mybatis/test/UserMapper.java | 11 ++ .../src/test/resources/application.properties | 5 + .../deployment/src/test/resources/insert.sql | 9 ++ extensions/mybatis/pom.xml | 21 +++ extensions/mybatis/runtime/pom.xml | 57 +++++++ .../mybatis/runtime/MyBatisProducers.java | 21 +++ .../mybatis/runtime/MyBatisRecorder.java | 88 +++++++++++ .../mybatis/runtime/MyBatisRuntimeConfig.java | 34 +++++ .../resources/META-INF/quarkus-extension.yaml | 9 ++ extensions/pom.xml | 1 + 19 files changed, 604 insertions(+) create mode 100644 extensions/mybatis/deployment/pom.xml create mode 100644 extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MyBatisMapperBuildItem.java create mode 100644 extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MybatisProcessor.java create mode 100644 extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionFactoryBuildItem.java create mode 100644 extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/SqlSessionManagerBuildItem.java create mode 100644 extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/MyBatisTest.java create mode 100644 extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/TestResources.java create mode 100644 extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/User.java create mode 100644 extensions/mybatis/deployment/src/test/java/io/quarkus/mybatis/test/UserMapper.java create mode 100644 extensions/mybatis/deployment/src/test/resources/application.properties create mode 100644 extensions/mybatis/deployment/src/test/resources/insert.sql create mode 100644 extensions/mybatis/pom.xml create mode 100644 extensions/mybatis/runtime/pom.xml create mode 100644 extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisProducers.java create mode 100644 extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRecorder.java create mode 100644 extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java create mode 100644 extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 3f879915b7db3..558da26d886af 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/extensions/mybatis/deployment/pom.xml b/extensions/mybatis/deployment/pom.xml new file mode 100644 index 0000000000000..1e33629cfd66d --- /dev/null +++ b/extensions/mybatis/deployment/pom.xml @@ -0,0 +1,69 @@ + + + + quarkus-mybatis-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-mybatis-deployment + Quarkus - Mybatis - Deployment + + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-core-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 + + + + + + + org.apache.maven.plugins + 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..c2ca80c662d55 --- /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 dotName; + + public MyBatisMapperBuildItem(DotName dotName) { + this.dotName = dotName; + } + + public DotName getDotName() { + return dotName; + } +} 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..84f792b75d33b --- /dev/null +++ b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MybatisProcessor.java @@ -0,0 +1,140 @@ +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.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.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 addMyBatisMappers(BuildProducer mappers, + BuildProducer additionalBeans, + 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)); + additionalBeans.produce(new AdditionalBeanBuildItem(dotName.toString())); + } + } + } + + @BuildStep + void unremovableBeans(BuildProducer beanProducer) { + beanProducer.produce(AdditionalBeanBuildItem.unremovableOf(MyBatisProducers.class)); + } + + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + SqlSessionFactoryBuildItem generateSqlSessionFactory(MyBatisRuntimeConfig myBatisRuntimeConfig, + List myBatisMapperBuildItems, + List jdbcDataSourcesBuildItem, + MyBatisRecorder recorder) { + List mappers = myBatisMapperBuildItems + .stream().map(m -> m.getDotName().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.getDotName()) + .scope(Singleton.class) + .setRuntimeInit() + .unremovable() + .supplier(recorder.MyBatisMapperSupplier(i.getDotName().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..9790fde5601bb --- /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.initialSql=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..500a10d6192ee --- /dev/null +++ b/extensions/mybatis/runtime/pom.xml @@ -0,0 +1,57 @@ + + + + quarkus-mybatis-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-mybatis + Quarkus - Mybatis - Runtime + MyBatis SQL mapper framework for Java + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-agroal + + + org.mybatis + mybatis + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + org.apache.maven.plugins + 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..0f44e97f2bbf9 --- /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) { + + } + } + + 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 MyBatisMapperSupplier(String name, RuntimeValue sqlSessionManager) { + return () -> { + try { + return sqlSessionManager.getValue().getMapper(Resources.classForName(name)); + } catch (ClassNotFoundException e) { + return null; + } + }; + } + + public void runInitialSql(RuntimeValue sqlSessionFactory, String sql) { + try (SqlSession session = sqlSessionFactory.getValue().openSession()) { + Connection conn = session.getConnection(); + Reader reader = Resources.getResourceAsReader(sql); + ScriptRunner runner = new ScriptRunner(conn); + runner.setLogWriter(null); + runner.runScript(reader); + reader.close(); + } catch (Exception e) { + LOG.warn("Error executing SQL Script " + sql); + } + } + + public void register(RuntimeValue sqlSessionFactory, BeanContainer beanContainer) { + beanContainer.instance(MyBatisProducers.class).setSqlSessionFactory(sqlSessionFactory.getValue()); + } +} diff --git a/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java new file mode 100644 index 0000000000000..57cbc2aa107a6 --- /dev/null +++ b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java @@ -0,0 +1,34 @@ +package io.quarkus.mybatis.runtime; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "mybatis") +public class MyBatisRuntimeConfig { + + /** + * MyBatis environment id + */ + @ConfigItem(defaultValue = "quarkus") + public String environment = "quarkus"; + + /** + * MyBatis transaction factory + */ + @ConfigItem(defaultValue = "MANAGED") + public String transactionFactory = "MANAGED"; + + /** + * MyBatis data source + */ + @ConfigItem(name = "datasource") + public Optional dataSource; + + /** + * MyBatis initial sql + */ + @ConfigItem(name = "initialSql") + public Optional initialSql; +} diff --git a/extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..937196a0a3161 --- /dev/null +++ b/extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +--- +name: "MyBatis SQL Mapper" +metadata: + keywords: + - "mybatis" + guide: "https://quarkus.io/guides/mybatis" + categories: + - "data" + status: "stable" diff --git a/extensions/pom.xml b/extensions/pom.xml index ed53e95a036dc..a51ebcaec2b97 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -100,6 +100,7 @@ tika neo4j mongodb-client + mybatis artemis-core artemis-jms avro From 33de135c08926643eb507843dab2dea74a34ad87 Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Tue, 14 Jul 2020 22:20:55 +0800 Subject: [PATCH 2/8] add integration tests --- bom/application/pom.xml | 2 +- extensions/mybatis/deployment/pom.xml | 5 - .../deployment/MyBatisMapperBuildItem.java | 10 +- .../mybatis/deployment/MybatisProcessor.java | 30 +++-- .../src/test/resources/application.properties | 2 +- extensions/mybatis/runtime/pom.xml | 5 - .../mybatis/runtime/MyBatisRecorder.java | 2 +- .../mybatis/runtime/MyBatisRuntimeConfig.java | 6 +- integration-tests/mybatis/pom.xml | 118 ++++++++++++++++++ .../quarkus/it/mybatis/MybatisResource.java | 58 +++++++++ .../main/java/io/quarkus/it/mybatis/User.java | 22 ++++ .../io/quarkus/it/mybatis/UserMapper.java | 20 +++ .../src/main/resources/application.properties | 5 + .../mybatis/src/main/resources/insert.sql | 9 ++ .../java/io/quarkus/it/mybatis/MybatisIT.java | 8 ++ .../io/quarkus/it/mybatis/MybatisTest.java | 25 ++++ .../io/quarkus/it/mybatis/TestResources.java | 8 ++ integration-tests/pom.xml | 1 + 18 files changed, 308 insertions(+), 28 deletions(-) create mode 100644 integration-tests/mybatis/pom.xml create mode 100644 integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java create mode 100644 integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java create mode 100644 integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java create mode 100644 integration-tests/mybatis/src/main/resources/application.properties create mode 100644 integration-tests/mybatis/src/main/resources/insert.sql create mode 100644 integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisIT.java create mode 100644 integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisTest.java create mode 100644 integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/TestResources.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 558da26d886af..100a8f761eb7b 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -2891,7 +2891,7 @@ org.mybatis mybatis - ${mybatis.version} + ${mybatis.version} net.minidev diff --git a/extensions/mybatis/deployment/pom.xml b/extensions/mybatis/deployment/pom.xml index 1e33629cfd66d..0ac31c599cde6 100644 --- a/extensions/mybatis/deployment/pom.xml +++ b/extensions/mybatis/deployment/pom.xml @@ -18,10 +18,6 @@ io.quarkus quarkus-arc-deployment - - io.quarkus - quarkus-core-deployment - io.quarkus quarkus-agroal-deployment @@ -51,7 +47,6 @@ - org.apache.maven.plugins maven-compiler-plugin 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 index c2ca80c662d55..925d79c243aef 100644 --- 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 @@ -5,13 +5,13 @@ import io.quarkus.builder.item.MultiBuildItem; public final class MyBatisMapperBuildItem extends MultiBuildItem { - private final DotName dotName; + private final DotName mapperName; - public MyBatisMapperBuildItem(DotName dotName) { - this.dotName = dotName; + public MyBatisMapperBuildItem(DotName mapperName) { + this.mapperName = mapperName; } - public DotName getDotName() { - return dotName; + 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 index 84f792b75d33b..4ef50bb892e1a 100644 --- 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 @@ -7,6 +7,10 @@ import javax.inject.Singleton; import org.apache.ibatis.annotations.Mapper; +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; @@ -22,6 +26,8 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +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; @@ -39,14 +45,24 @@ FeatureBuildItem feature() { } @BuildStep - void addMyBatisMappers(BuildProducer mappers, - BuildProducer additionalBeans, - CombinedIndexBuildItem indexBuildItem) { + 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)); + } + + @BuildStep + void addMyBatisMappers(BuildProducer mappers, 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)); - additionalBeans.produce(new AdditionalBeanBuildItem(dotName.toString())); } } } @@ -63,7 +79,7 @@ SqlSessionFactoryBuildItem generateSqlSessionFactory(MyBatisRuntimeConfig myBati List jdbcDataSourcesBuildItem, MyBatisRecorder recorder) { List mappers = myBatisMapperBuildItems - .stream().map(m -> m.getDotName().toString()).collect(Collectors.toList()); + .stream().map(m -> m.getMapperName().toString()).collect(Collectors.toList()); String dataSourceName; if (myBatisRuntimeConfig.dataSource.isPresent()) { @@ -109,11 +125,11 @@ void generateMapperBeans(MyBatisRecorder recorder, for (MyBatisMapperBuildItem i : myBatisMapperBuildItems) { SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem - .configure(i.getDotName()) + .configure(i.getMapperName()) .scope(Singleton.class) .setRuntimeInit() .unremovable() - .supplier(recorder.MyBatisMapperSupplier(i.getDotName().toString(), + .supplier(recorder.MyBatisMapperSupplier(i.getMapperName().toString(), sqlSessionManagerBuildItem.getSqlSessionManager())); syntheticBeanBuildItemBuildProducer.produce(configurator.done()); } diff --git a/extensions/mybatis/deployment/src/test/resources/application.properties b/extensions/mybatis/deployment/src/test/resources/application.properties index 9790fde5601bb..cfdfa74d72918 100644 --- a/extensions/mybatis/deployment/src/test/resources/application.properties +++ b/extensions/mybatis/deployment/src/test/resources/application.properties @@ -2,4 +2,4 @@ quarkus.datasource.db-kind=h2 quarkus.datasource.username=username-default quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:default -quarkus.mybatis.initialSql=insert.sql +quarkus.mybatis.initial-sql=insert.sql diff --git a/extensions/mybatis/runtime/pom.xml b/extensions/mybatis/runtime/pom.xml index 500a10d6192ee..d38c3c8250f0e 100644 --- a/extensions/mybatis/runtime/pom.xml +++ b/extensions/mybatis/runtime/pom.xml @@ -15,10 +15,6 @@ MyBatis SQL mapper framework for Java - - io.quarkus - quarkus-core - io.quarkus quarkus-arc @@ -40,7 +36,6 @@ quarkus-bootstrap-maven-plugin - org.apache.maven.plugins maven-compiler-plugin 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 index 0f44e97f2bbf9..8080ab38c5a3f 100644 --- 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 @@ -46,7 +46,7 @@ public RuntimeValue createSqlSessionFactory( try { configuration.addMapper(Resources.classForName(mapper)); } catch (ClassNotFoundException e) { - + LOG.debug("Can not find the mapper class " + mapper); } } diff --git a/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java index 57cbc2aa107a6..0bf8c5a7df05c 100644 --- a/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java +++ b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/MyBatisRuntimeConfig.java @@ -12,13 +12,13 @@ public class MyBatisRuntimeConfig { * MyBatis environment id */ @ConfigItem(defaultValue = "quarkus") - public String environment = "quarkus"; + public String environment; /** * MyBatis transaction factory */ @ConfigItem(defaultValue = "MANAGED") - public String transactionFactory = "MANAGED"; + public String transactionFactory; /** * MyBatis data source @@ -29,6 +29,6 @@ public class MyBatisRuntimeConfig { /** * MyBatis initial sql */ - @ConfigItem(name = "initialSql") + @ConfigItem(name = "initial-sql") public Optional initialSql; } diff --git a/integration-tests/mybatis/pom.xml b/integration-tests/mybatis/pom.xml new file mode 100644 index 0000000000000..a5b4531ee8b17 --- /dev/null +++ b/integration-tests/mybatis/pom.xml @@ -0,0 +1,118 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-mybatis-integration-test + Quarkus - Integration Tests - MyBatis + The mybatis integration tests module + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-jackson + + + io.quarkus + quarkus-mybatis + + + io.quarkus + quarkus-jdbc-h2 + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-test-h2 + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + + diff --git a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java new file mode 100644 index 0000000000000..e26db275156dc --- /dev/null +++ b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.quarkus.it.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; + + @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); + } + +} diff --git a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java new file mode 100644 index 0000000000000..0afa92a2fad78 --- /dev/null +++ b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java @@ -0,0 +1,22 @@ +package io.quarkus.it.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; + } +} diff --git a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java new file mode 100644 index 0000000000000..2188e65824052 --- /dev/null +++ b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java @@ -0,0 +1,20 @@ +package io.quarkus.it.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); + + @Insert("insert into users (id, name) values (#{id}, #{name})") + Integer createUser(@Param("id") Integer id, @Param("name") String name); + + @Delete("delete from users where id = #{id}") + Integer removeUser(Integer id); +} diff --git a/integration-tests/mybatis/src/main/resources/application.properties b/integration-tests/mybatis/src/main/resources/application.properties new file mode 100644 index 0000000000000..cfdfa74d72918 --- /dev/null +++ b/integration-tests/mybatis/src/main/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/integration-tests/mybatis/src/main/resources/insert.sql b/integration-tests/mybatis/src/main/resources/insert.sql new file mode 100644 index 0000000000000..a6edad4e71398 --- /dev/null +++ b/integration-tests/mybatis/src/main/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/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisIT.java b/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisIT.java new file mode 100644 index 0000000000000..f5a043f3a765b --- /dev/null +++ b/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisIT.java @@ -0,0 +1,8 @@ +package io.quarkus.it.mybatis; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class MybatisIT extends MybatisTest { + +} diff --git a/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisTest.java b/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisTest.java new file mode 100644 index 0000000000000..ebf9e9d23f817 --- /dev/null +++ b/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/MybatisTest.java @@ -0,0 +1,25 @@ +package io.quarkus.it.mybatis; + +import static org.hamcrest.core.Is.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +class MybatisTest { + + @Test + public void test() { + RestAssured.when().get("/mybatis/user/1").then() + .body(is("{\"id\":1,\"name\":\"Test User1\"}")); + + RestAssured.given().param("id", "5").param("name", "New User").post("/mybatis/user") + .then().body(is("1")); + + RestAssured.when().delete("/mybatis/user/1").then() + .body(is("1")); + } + +} diff --git a/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/TestResources.java b/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/TestResources.java new file mode 100644 index 0000000000000..d50dcae756daa --- /dev/null +++ b/integration-tests/mybatis/src/test/java/io/quarkus/it/mybatis/TestResources.java @@ -0,0 +1,8 @@ +package io.quarkus.it.mybatis; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index c6146c8e36e42..c20b36a78e1aa 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -88,6 +88,7 @@ tika neo4j mongodb-client + mybatis jackson jsonb resteasy-jackson From 32414b7accdd74d105e0e751694f67e77aaf2084 Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Fri, 17 Jul 2020 11:42:24 +0800 Subject: [PATCH 3/8] native support --- .../mybatis/deployment/MybatisProcessor.java | 20 ++++++++++++++++++- integration-tests/mybatis/pom.xml | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) 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 index 4ef50bb892e1a..c26e02a9aad82 100644 --- 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 @@ -7,6 +7,7 @@ import javax.inject.Singleton; import org.apache.ibatis.annotations.Mapper; +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; @@ -26,6 +27,8 @@ 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; @@ -55,14 +58,22 @@ void reflectiveClasses(BuildProducer reflectiveClass) ProxyFactory.class, XMLLanguageDriver.class, RawLanguageDriver.class)); + + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, + PerpetualCache.class)); } @BuildStep - void addMyBatisMappers(BuildProducer mappers, CombinedIndexBuildItem indexBuildItem) { + 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())); } } } @@ -72,6 +83,13 @@ 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, diff --git a/integration-tests/mybatis/pom.xml b/integration-tests/mybatis/pom.xml index a5b4531ee8b17..de9b9ab816384 100644 --- a/integration-tests/mybatis/pom.xml +++ b/integration-tests/mybatis/pom.xml @@ -74,6 +74,9 @@ native + + --report-unsupported-elements-at-runtime + From 269e7248729b2c0189bf44e941d45dc8e6088217 Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Fri, 17 Jul 2020 14:43:35 +0800 Subject: [PATCH 4/8] make sure it works with the graalvm jdk8 native image --- extensions/mybatis/runtime/pom.xml | 4 +++ .../graal/DefineClassHelperSubstitution.java | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/DefineClassHelperSubstitution.java diff --git a/extensions/mybatis/runtime/pom.xml b/extensions/mybatis/runtime/pom.xml index d38c3c8250f0e..ee55066abf1e3 100644 --- a/extensions/mybatis/runtime/pom.xml +++ b/extensions/mybatis/runtime/pom.xml @@ -27,6 +27,10 @@ org.mybatis mybatis + + org.graalvm.nativeimage + svm + diff --git a/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/DefineClassHelperSubstitution.java b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/DefineClassHelperSubstitution.java new file mode 100644 index 0000000000000..382444d04850d --- /dev/null +++ b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/DefineClassHelperSubstitution.java @@ -0,0 +1,25 @@ +package io.quarkus.mybatis.runtime.graal; + +import java.lang.invoke.MethodHandles; + +import org.apache.ibatis.javassist.CannotCompileException; +import org.apache.ibatis.javassist.util.proxy.DefineClassHelper; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.JDK8OrEarlier; + +@TargetClass(value = DefineClassHelper.class, onlyWith = JDK8OrEarlier.class) +final public class DefineClassHelperSubstitution { + + @Substitute + public static Class toClass(MethodHandles.Lookup lookup, byte[] bcode) throws CannotCompileException { + throw new CannotCompileException("Not support"); + } + + @Substitute + static Class toPublicClass(String className, byte[] bcode) throws CannotCompileException { + throw new CannotCompileException("Not support"); + } + +} From 2a5e118ba8262e6e65aa12ba7776c052d700c375 Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Sat, 18 Jul 2020 00:02:40 +0800 Subject: [PATCH 5/8] add the mybatis.adoc --- docs/src/main/asciidoc/mybatis.adoc | 271 ++++++++++++++++++ .../resources/META-INF/quarkus-extension.yaml | 2 +- .../quarkus/it/mybatis/MybatisResource.java | 16 -- 3 files changed, 272 insertions(+), 17 deletions(-) create mode 100644 docs/src/main/asciidoc/mybatis.adoc diff --git a/docs/src/main/asciidoc/mybatis.adoc b/docs/src/main/asciidoc/mybatis.adoc new file mode 100644 index 0000000000000..0595ba213f60c --- /dev/null +++ b/docs/src/main/asciidoc/mybatis.adoc @@ -0,0 +1,271 @@ +//// +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 the link:https://mybatis.org/mybatis-3/[MyBatis] to support the 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,resteay-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] +---- + + org.amqphub.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. + +== Createing 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 +---- + +== Configuration References +include::{generated-dir}/config/quarkus-mybatis.adoc[opts=optional, leveloffset=+1] diff --git a/extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 937196a0a3161..2ecf549c519d8 100644 --- a/extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/mybatis/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -6,4 +6,4 @@ metadata: guide: "https://quarkus.io/guides/mybatis" categories: - "data" - status: "stable" + status: "preview" diff --git a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java index e26db275156dc..e92076fca3497 100644 --- a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java +++ b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/MybatisResource.java @@ -1,19 +1,3 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package io.quarkus.it.mybatis; import javax.inject.Inject; From d9abb08cdfef88ee330b794ab9373901165df0a6 Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Sat, 18 Jul 2020 08:45:30 +0800 Subject: [PATCH 6/8] fix minor typos --- docs/src/main/asciidoc/mybatis.adoc | 6 +++--- integration-tests/mybatis/pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/asciidoc/mybatis.adoc b/docs/src/main/asciidoc/mybatis.adoc index 0595ba213f60c..2f945bfb3bf7f 100644 --- a/docs/src/main/asciidoc/mybatis.adoc +++ b/docs/src/main/asciidoc/mybatis.adoc @@ -7,7 +7,7 @@ https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc include::./attributes.adoc[] :extension-status: preview -This guide demonstrates how your Quarkus application can use the link:https://mybatis.org/mybatis-3/[MyBatis] to support the custom SQL, stored procedures and advanced mappings. +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[] @@ -40,7 +40,7 @@ First, we need a new project. Create a new project with the following command: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=mybatis-quickstart \ - -Dextensions="mybatis,resteay-jackson,jdbc-mysql" + -Dextensions="mybatis,restesay-jackson,jdbc-mysql" cd mybatis-quickstart ---- This command generates a Maven project, with its pom.xml importing the quarkus-mybatis extension. @@ -125,7 +125,7 @@ public interface UserMapper { 2. Insert a user into the database. We should use the `@Param` to bind the parameters. 3. Delete a user from the databse. -== Createing the MyBatisResource to handle the requests +== 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. diff --git a/integration-tests/mybatis/pom.xml b/integration-tests/mybatis/pom.xml index de9b9ab816384..263958e91ee38 100644 --- a/integration-tests/mybatis/pom.xml +++ b/integration-tests/mybatis/pom.xml @@ -76,7 +76,7 @@ --report-unsupported-elements-at-runtime - + From 4ed8e85a703c6c2a206180e4d1ba8c658035ae91 Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Wed, 22 Jul 2020 15:22:37 +0800 Subject: [PATCH 7/8] fix groupId --- docs/src/main/asciidoc/mybatis.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/mybatis.adoc b/docs/src/main/asciidoc/mybatis.adoc index 2f945bfb3bf7f..6b5fba47a4ad6 100644 --- a/docs/src/main/asciidoc/mybatis.adoc +++ b/docs/src/main/asciidoc/mybatis.adoc @@ -58,7 +58,7 @@ This will add the following to your `pom.xml`: [source] ---- - org.amqphub.quarkus + io.quarkus quarkus-mybatis ---- From 2656ef192e8c3c82df3cf74fac60a6762bd127f3 Mon Sep 17 00:00:00 2001 From: Amos Feng Date: Wed, 29 Jul 2020 22:33:56 +0800 Subject: [PATCH 8/8] update to test the CacheNamespace --- docs/src/main/asciidoc/mybatis.adoc | 3 +++ .../mybatis/deployment/MybatisProcessor.java | 3 ++- .../graal/SerializedCacheSubstitution.java | 18 ++++++++++++++++++ .../main/java/io/quarkus/it/mybatis/User.java | 4 +++- .../java/io/quarkus/it/mybatis/UserMapper.java | 2 ++ 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/SerializedCacheSubstitution.java diff --git a/docs/src/main/asciidoc/mybatis.adoc b/docs/src/main/asciidoc/mybatis.adoc index 6b5fba47a4ad6..f8e343022706a 100644 --- a/docs/src/main/asciidoc/mybatis.adoc +++ b/docs/src/main/asciidoc/mybatis.adoc @@ -267,5 +267,8 @@ and then run with: ./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/src/main/java/io/quarkus/mybatis/deployment/MybatisProcessor.java b/extensions/mybatis/deployment/src/main/java/io/quarkus/mybatis/deployment/MybatisProcessor.java index c26e02a9aad82..db6d242ebe50d 100644 --- 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 @@ -7,6 +7,7 @@ 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; @@ -60,7 +61,7 @@ void reflectiveClasses(BuildProducer reflectiveClass) RawLanguageDriver.class)); reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, - PerpetualCache.class)); + PerpetualCache.class, LruCache.class)); } @BuildStep diff --git a/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/SerializedCacheSubstitution.java b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/SerializedCacheSubstitution.java new file mode 100644 index 0000000000000..a04684e6d3caa --- /dev/null +++ b/extensions/mybatis/runtime/src/main/java/io/quarkus/mybatis/runtime/graal/SerializedCacheSubstitution.java @@ -0,0 +1,18 @@ +package io.quarkus.mybatis.runtime.graal; + +import java.io.Serializable; + +import org.apache.ibatis.cache.CacheException; +import org.apache.ibatis.cache.decorators.SerializedCache; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(SerializedCache.class) +final public class SerializedCacheSubstitution { + + @Substitute + private byte[] serialize(Serializable value) { + throw new CacheException("ObjectOutputStream.writeObject is unsupported in Graal VM"); + } +} diff --git a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java index 0afa92a2fad78..9e6c0b0e5c36e 100644 --- a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java +++ b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/User.java @@ -1,6 +1,8 @@ package io.quarkus.it.mybatis; -public class User { +import java.io.Serializable; + +public class User implements Serializable { private Integer id; private String name; diff --git a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java index 2188e65824052..fc7e7a9280871 100644 --- a/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java +++ b/integration-tests/mybatis/src/main/java/io/quarkus/it/mybatis/UserMapper.java @@ -1,5 +1,6 @@ package io.quarkus.it.mybatis; +import org.apache.ibatis.annotations.CacheNamespace; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; @@ -7,6 +8,7 @@ import org.apache.ibatis.annotations.Select; @Mapper +@CacheNamespace(readWrite = false) public interface UserMapper { @Select("select * from users where id = #{id}")