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

Allow to specify a wildcard at typeAliasesPackage and typeHandlersPackage #359

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
79 changes: 56 additions & 23 deletions src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,21 @@
*/
package org.mybatis.spring;

import static org.springframework.util.Assert.notNull;
import static org.springframework.util.Assert.state;
import static org.springframework.util.ObjectUtils.isEmpty;
import static org.springframework.util.StringUtils.hasLength;
import static org.springframework.util.StringUtils.tokenizeToStringArray;

import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Stream;

import javax.sql.DataSource;

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
Expand All @@ -55,7 +52,19 @@
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.util.ClassUtils;

import static org.springframework.util.Assert.notNull;
import static org.springframework.util.Assert.state;
import static org.springframework.util.ObjectUtils.isEmpty;
import static org.springframework.util.StringUtils.hasLength;
import static org.springframework.util.StringUtils.tokenizeToStringArray;

/**
* {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}.
Expand All @@ -79,6 +88,9 @@ public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, In

private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);

private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

private Resource configLocation;

private Configuration configuration;
Expand Down Expand Up @@ -211,6 +223,8 @@ public void setPlugins(Interceptor[] plugins) {
/**
* Packages to search for type aliases.
*
* <p>Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.model}.
*
* @since 1.0.1
*
* @param typeAliasesPackage package to scan for domain objects
Expand All @@ -236,6 +250,8 @@ public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
/**
* Packages to search for type handlers.
*
* <p>Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.typehandler}.

* @since 1.0.1
*
* @param typeHandlersPackage package to scan for type handlers
Expand Down Expand Up @@ -416,9 +432,9 @@ public void afterPropertiesSet() throws Exception {
* Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
*
* @return SqlSessionFactory
* @throws IOException if loading the config file failed
* @throws Exception if configuration is failed
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

final Configuration targetConfiguration;

Expand All @@ -444,13 +460,8 @@ protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Stream.of(typeAliasPackageArray).forEach(packageToScan -> {
targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
});
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType)
.forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}

if (!isEmpty(this.typeAliases)) {
Expand All @@ -468,12 +479,11 @@ protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
}

if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Stream.of(typeHandlersPackageArray).forEach(packageToScan -> {
targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
});
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream()
.filter(clazz -> !clazz.isInterface())
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}

if (!isEmpty(this.typeHandlers)) {
Expand Down Expand Up @@ -571,4 +581,27 @@ public void onApplicationEvent(ApplicationEvent event) {
}
}

private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType)
throws IOException {
Set<Class<?>> classes = new HashSet<>();
String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packagePattern : packagePatternArray) {
Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
for (Resource resource : resources) {
try {
ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
classes.add(clazz);
}
} catch (Throwable e) {
LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
}
}
}
return classes;
}

}
28 changes: 25 additions & 3 deletions src/test/java/org/mybatis/spring/SqlSessionFactoryBeanTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ void testAddATypeAlias() throws Exception {
@Test
void testSearchATypeAliasPackage() throws Exception {
setupFactoryBean();
factoryBean.setTypeAliasesPackage("org/mybatis/spring/type");
factoryBean.setTypeAliasesPackage("org.mybatis.spring.type");

TypeAliasRegistry typeAliasRegistry = factoryBean.getObject().getConfiguration().getTypeAliasRegistry();
typeAliasRegistry.resolveAlias("testAlias");
Expand All @@ -354,7 +354,7 @@ void testSearchATypeAliasPackage() throws Exception {
void testSearchATypeAliasPackageWithSuperType() throws Exception {
setupFactoryBean();
factoryBean.setTypeAliasesSuperType(SuperType.class);
factoryBean.setTypeAliasesPackage("org/mybatis/spring/type");
factoryBean.setTypeAliasesPackage("org.mybatis.*.type");

TypeAliasRegistry typeAliasRegistry = factoryBean.getObject().getConfiguration().getTypeAliasRegistry();
typeAliasRegistry.resolveAlias("testAlias2");
Expand All @@ -364,10 +364,32 @@ void testSearchATypeAliasPackageWithSuperType() throws Exception {
assertThrows(TypeException.class, () -> typeAliasRegistry.resolveAlias("dummyTypeHandler"));
}

@Test
void testSearchATypeAliasPackageWithSamePackage() throws Exception {
setupFactoryBean();
factoryBean.setTypeAliasesPackage("org.mybatis.spring.type, org.*.spring.type");

TypeAliasRegistry typeAliasRegistry = factoryBean.getObject().getConfiguration().getTypeAliasRegistry();
typeAliasRegistry.resolveAlias("testAlias");
typeAliasRegistry.resolveAlias("testAlias2");
typeAliasRegistry.resolveAlias("dummyTypeHandler");
typeAliasRegistry.resolveAlias("superType");
}

@Test
void testSearchATypeHandlerPackage() throws Exception {
setupFactoryBean();
factoryBean.setTypeHandlersPackage("org/mybatis/spring/type");
factoryBean.setTypeHandlersPackage("org.**.type");

TypeHandlerRegistry typeHandlerRegistry = factoryBean.getObject().getConfiguration().getTypeHandlerRegistry();
assertThat(typeHandlerRegistry.hasTypeHandler(BigInteger.class)).isTrue();
assertThat(typeHandlerRegistry.hasTypeHandler(BigDecimal.class)).isTrue();
}

@Test
void testSearchATypeHandlerPackageWithSamePackage() throws Exception {
setupFactoryBean();
factoryBean.setTypeHandlersPackage("org.mybatis.spring.type, org.mybatis.*.type");

TypeHandlerRegistry typeHandlerRegistry = factoryBean.getObject().getConfiguration().getTypeHandlerRegistry();
assertThat(typeHandlerRegistry.hasTypeHandler(BigInteger.class)).isTrue();
Expand Down