Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support appendable method on SqlSessionFactoryBean #816

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p>
* This is an alternative to specifying "&lt;sqlmapper&gt;" 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> T[] appendArrays(T[] oldArrays, T[] newArrays, IntFunction<T[]> generator) {
if (oldArrays == null) {
return newArrays;
} else {
if (newArrays == null) {
return oldArrays;
} else {
List<T> newList = new ArrayList<>(Arrays.asList(oldArrays));
newList.addAll(Arrays.asList(newArrays));
return newList.toArray(generator.apply(0));
}
}
}

/**
* {@inheritDoc}
*/
Expand Down
163 changes: 162 additions & 1 deletion src/test/java/org/mybatis/spring/SqlSessionFactoryBeanTest.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<String> {

@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 {
}

Expand Down
28 changes: 28 additions & 0 deletions src/test/java/org/mybatis/spring/TestMapper2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

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.
You may obtain a copy of the License at

https://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.

-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.spring.TestMapper2">

<select id="selectOne" resultType="int">
SELECT 1
</select>

</mapper>
28 changes: 28 additions & 0 deletions src/test/java/org/mybatis/spring/TestMapper3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

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.
You may obtain a copy of the License at

https://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.

-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.spring.TestMapper3">

<select id="selectOne" resultType="int">
SELECT 1
</select>

</mapper>