diff --git a/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java b/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java
index 0255a7075c..c4032f142c 100644
--- a/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java
+++ b/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java
@@ -24,10 +24,14 @@
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
+import java.util.function.IntFunction;
import java.util.stream.Stream;
import javax.sql.DataSource;
@@ -478,6 +482,88 @@ public void setDefaultScriptingLanguageDriver(Class extends LanguageDriver> de
this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
}
+ /**
+ * Add locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration
+ * at runtime.
+ *
+ * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. This property being
+ * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g.
+ * "classpath*:sqlmap/*-mapper.xml".
+ *
+ * @param mapperLocations
+ * location of MyBatis mapper files
+ *
+ * @see #setMapperLocations(Resource...)
+ *
+ * @since 3.0.2
+ */
+ public void addMapperLocations(Resource... mapperLocations) {
+ setMapperLocations(appendArrays(this.mapperLocations, mapperLocations, Resource[]::new));
+ }
+
+ /**
+ * Add type handlers.
+ *
+ * @param typeHandlers
+ * Type handler list
+ *
+ * @since 3.0.2
+ */
+ public void addTypeHandlers(TypeHandler>... typeHandlers) {
+ setTypeHandlers(appendArrays(this.typeHandlers, typeHandlers, TypeHandler[]::new));
+ }
+
+ /**
+ * Add scripting language drivers.
+ *
+ * @param scriptingLanguageDrivers
+ * scripting language drivers
+ *
+ * @since 3.0.2
+ */
+ public void addScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
+ setScriptingLanguageDrivers(
+ appendArrays(this.scriptingLanguageDrivers, scriptingLanguageDrivers, LanguageDriver[]::new));
+ }
+
+ /**
+ * Add Mybatis plugins.
+ *
+ * @param plugins
+ * list of plugins
+ *
+ * @since 3.0.2
+ */
+ public void addPlugins(Interceptor... plugins) {
+ setPlugins(appendArrays(this.plugins, plugins, Interceptor[]::new));
+ }
+
+ /**
+ * Add type aliases.
+ *
+ * @param typeAliases
+ * Type aliases list
+ *
+ * @since 3.0.2
+ */
+ public void addTypeAliases(Class>... typeAliases) {
+ setTypeAliases(appendArrays(this.typeAliases, typeAliases, Class[]::new));
+ }
+
+ private T[] appendArrays(T[] oldArrays, T[] newArrays, IntFunction generator) {
+ if (oldArrays == null) {
+ return newArrays;
+ } else {
+ if (newArrays == null) {
+ return oldArrays;
+ } else {
+ List newList = new ArrayList<>(Arrays.asList(oldArrays));
+ newList.addAll(Arrays.asList(newArrays));
+ return newList.toArray(generator.apply(0));
+ }
+ }
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/test/java/org/mybatis/spring/SqlSessionFactoryBeanTest.java b/src/test/java/org/mybatis/spring/SqlSessionFactoryBeanTest.java
index 7ada0e4164..90fda72eb8 100644
--- a/src/test/java/org/mybatis/spring/SqlSessionFactoryBeanTest.java
+++ b/src/test/java/org/mybatis/spring/SqlSessionFactoryBeanTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2022 the original author or authors.
+ * Copyright 2010-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,13 +22,24 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
import org.apache.ibatis.cache.impl.PerpetualCache;
+import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.io.JBoss6VFS;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
@@ -41,9 +52,12 @@
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.EnumOrdinalTypeHandler;
+import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeAliasRegistry;
import org.apache.ibatis.type.TypeException;
+import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.junit.jupiter.api.Test;
import org.mybatis.core.jdk.type.AtomicNumberTypeHandler;
@@ -492,6 +506,88 @@ void testScriptingLanguageDriverWithDefault() throws Exception {
assertThat(registry.getDriver(RawLanguageDriver.class)).isNotNull();
}
+ @Test
+ void testAppendableMethod() throws Exception {
+ setupFactoryBean();
+ // add values
+ this.factoryBean.addScriptingLanguageDrivers(new MyLanguageDriver1());
+ this.factoryBean.addScriptingLanguageDrivers(new MyLanguageDriver2());
+ this.factoryBean.addPlugins(new MyPlugin1(), new MyPlugin2());
+ this.factoryBean.addPlugins(new MyPlugin3());
+ this.factoryBean.addTypeHandlers(new MyTypeHandler1());
+ this.factoryBean.addTypeHandlers(new MyTypeHandler2(), new MyTypeHandler3());
+ this.factoryBean.addTypeAliases(MyTypeHandler1.class, MyTypeHandler2.class, MyTypeHandler3.class);
+ this.factoryBean.addTypeAliases(MyPlugin1.class);
+ this.factoryBean.addMapperLocations(new ClassPathResource("org/mybatis/spring/TestMapper.xml"),
+ new ClassPathResource("org/mybatis/spring/TestMapper2.xml"));
+ this.factoryBean.addMapperLocations(new ClassPathResource("org/mybatis/spring/TestMapper3.xml"));
+ // ignore null value
+ this.factoryBean.addScriptingLanguageDrivers(null);
+ this.factoryBean.addPlugins(null);
+ this.factoryBean.addTypeHandlers(null);
+ this.factoryBean.addTypeAliases(null);
+ this.factoryBean.addMapperLocations(null);
+ SqlSessionFactory factory = this.factoryBean.getObject();
+ LanguageDriverRegistry languageDriverRegistry = factory.getConfiguration().getLanguageRegistry();
+ TypeHandlerRegistry typeHandlerRegistry = factory.getConfiguration().getTypeHandlerRegistry();
+ TypeAliasRegistry typeAliasRegistry = factory.getConfiguration().getTypeAliasRegistry();
+ assertThat(languageDriverRegistry.getDriver(MyLanguageDriver1.class)).isNotNull();
+ assertThat(languageDriverRegistry.getDriver(MyLanguageDriver2.class)).isNotNull();
+ assertThat(typeHandlerRegistry.getTypeHandlers().stream().map(TypeHandler::getClass).map(Class::getSimpleName)
+ .collect(Collectors.toSet())).contains(MyTypeHandler1.class.getSimpleName(),
+ MyTypeHandler2.class.getSimpleName(), MyTypeHandler3.class.getSimpleName());
+ assertThat(typeAliasRegistry.getTypeAliases()).containsKeys(MyTypeHandler1.class.getSimpleName().toLowerCase(),
+ MyTypeHandler2.class.getSimpleName().toLowerCase(), MyTypeHandler3.class.getSimpleName().toLowerCase(),
+ MyPlugin1.class.getSimpleName().toLowerCase());
+ assertThat(factory.getConfiguration().getMappedStatement("org.mybatis.spring.TestMapper.findFail")).isNotNull();
+ assertThat(factory.getConfiguration().getMappedStatement("org.mybatis.spring.TestMapper2.selectOne")).isNotNull();
+ assertThat(factory.getConfiguration().getMappedStatement("org.mybatis.spring.TestMapper3.selectOne")).isNotNull();
+ assertThat(
+ factory.getConfiguration().getInterceptors().stream().map(Interceptor::getClass).map(Class::getSimpleName))
+ .contains(MyPlugin1.class.getSimpleName(), MyPlugin2.class.getSimpleName(),
+ MyPlugin3.class.getSimpleName());
+ }
+
+ @Test
+ void testAppendableMethodWithEmpty() throws Exception {
+ setupFactoryBean();
+ this.factoryBean.addScriptingLanguageDrivers();
+ this.factoryBean.addPlugins();
+ this.factoryBean.addTypeHandlers();
+ this.factoryBean.addTypeAliases();
+ this.factoryBean.addMapperLocations();
+ SqlSessionFactory factory = this.factoryBean.getObject();
+ LanguageDriverRegistry languageDriverRegistry = factory.getConfiguration().getLanguageRegistry();
+ TypeHandlerRegistry typeHandlerRegistry = factory.getConfiguration().getTypeHandlerRegistry();
+ TypeAliasRegistry typeAliasRegistry = factory.getConfiguration().getTypeAliasRegistry();
+ assertThat(languageDriverRegistry.getDriver(MyLanguageDriver1.class)).isNull();
+ assertThat(languageDriverRegistry.getDriver(MyLanguageDriver2.class)).isNull();
+ assertThat(typeHandlerRegistry.getTypeHandlers()).hasSize(40);
+ assertThat(typeAliasRegistry.getTypeAliases()).hasSize(80);
+ assertThat(factory.getConfiguration().getMappedStatementNames()).isEmpty();
+ assertThat(factory.getConfiguration().getInterceptors()).isEmpty();
+ }
+
+ @Test
+ void testAppendableMethodWithNull() throws Exception {
+ setupFactoryBean();
+ this.factoryBean.addScriptingLanguageDrivers(null);
+ this.factoryBean.addPlugins(null);
+ this.factoryBean.addTypeHandlers(null);
+ this.factoryBean.addTypeAliases(null);
+ this.factoryBean.addMapperLocations(null);
+ SqlSessionFactory factory = this.factoryBean.getObject();
+ LanguageDriverRegistry languageDriverRegistry = factory.getConfiguration().getLanguageRegistry();
+ TypeHandlerRegistry typeHandlerRegistry = factory.getConfiguration().getTypeHandlerRegistry();
+ TypeAliasRegistry typeAliasRegistry = factory.getConfiguration().getTypeAliasRegistry();
+ assertThat(languageDriverRegistry.getDriver(MyLanguageDriver1.class)).isNull();
+ assertThat(languageDriverRegistry.getDriver(MyLanguageDriver2.class)).isNull();
+ assertThat(typeHandlerRegistry.getTypeHandlers()).hasSize(40);
+ assertThat(typeAliasRegistry.getTypeAliases()).hasSize(80);
+ assertThat(factory.getConfiguration().getMappedStatementNames()).isEmpty();
+ assertThat(factory.getConfiguration().getInterceptors()).isEmpty();
+ }
+
private void assertDefaultConfig(SqlSessionFactory factory) {
assertConfig(factory, SqlSessionFactoryBean.class.getSimpleName(),
org.mybatis.spring.transaction.SpringManagedTransactionFactory.class);
@@ -522,6 +618,71 @@ private static class MyLanguageDriver1 extends RawLanguageDriver {
private static class MyLanguageDriver2 extends RawLanguageDriver {
}
+ private static class MyBasePlugin implements Interceptor {
+
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ return null;
+ }
+
+ @Override
+ public Object plugin(Object target) {
+ return Interceptor.super.plugin(target);
+ }
+
+ @Override
+ public void setProperties(Properties properties) {
+ Interceptor.super.setProperties(properties);
+ }
+ }
+
+ @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
+ private static class MyPlugin1 extends MyBasePlugin {
+
+ }
+
+ @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
+ private static class MyPlugin2 extends MyBasePlugin {
+
+ }
+
+ @Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
+ private static class MyPlugin3 extends MyBasePlugin {
+
+ }
+
+ private static class MyBaseTypeHandler extends BaseTypeHandler {
+
+ @Override
+ public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
+ throws SQLException {
+ }
+
+ @Override
+ public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
+ return null;
+ }
+
+ @Override
+ public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+ return null;
+ }
+
+ @Override
+ public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+ return null;
+ }
+ }
+
+ private static class MyTypeHandler1 extends MyBaseTypeHandler {
+ }
+
+ private static class MyTypeHandler2 extends MyBaseTypeHandler {
+ }
+
+ private static class MyTypeHandler3 extends MyBaseTypeHandler {
+ }
+
private static enum MyEnum {
}
diff --git a/src/test/java/org/mybatis/spring/TestMapper2.xml b/src/test/java/org/mybatis/spring/TestMapper2.xml
new file mode 100644
index 0000000000..89f5a09b92
--- /dev/null
+++ b/src/test/java/org/mybatis/spring/TestMapper2.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/src/test/java/org/mybatis/spring/TestMapper3.xml b/src/test/java/org/mybatis/spring/TestMapper3.xml
new file mode 100644
index 0000000000..36278e655d
--- /dev/null
+++ b/src/test/java/org/mybatis/spring/TestMapper3.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+