From 50cfbab79b36283f9b91d900dfe568cdbe2d1ece Mon Sep 17 00:00:00 2001 From: liuzh Date: Tue, 20 Aug 2024 21:27:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84spring=20boot=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 39 +- .../mapper-spring-boot-autoconfigure/pom.xml | 16 + .../MapperAutoConfiguration.java | 251 ++++++--- ...pendsOnDatabaseInitializationDetector.java | 24 + ...ybatisLanguageDriverAutoConfiguration.java | 136 +++++ .../autoconfigure/MybatisProperties.java | 529 +++++++++++++++++- .../mapper/autoconfigure/SpringBootVFS.java | 80 ++- .../SqlSessionFactoryBeanCustomizer.java | 21 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../src/main/resources/application.properties | 6 +- .../src/main/resources/logback.xml | 10 + .../mybatis/sample/SampleXmlApplication.java | 2 + .../src/main/resources/application.yml | 2 + .../src/main/resources/logback.xml | 10 + .../spring/annotation/MapperScans.java | 8 +- 15 files changed, 982 insertions(+), 153 deletions(-) create mode 100644 spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisDependsOnDatabaseInitializationDetector.java create mode 100644 spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisLanguageDriverAutoConfiguration.java create mode 100644 spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SqlSessionFactoryBeanCustomizer.java create mode 100644 spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/logback.xml create mode 100644 spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index 41868225d..da994339a 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ https://mybatis.io - 5.0.0-rc1 + 5.0.0-SNAPSHOT 17 17 UTF-8 @@ -44,10 +44,14 @@ 3.1.0 2.0.16 3.5.16 + 1.2.4 + 2.1.2 + 1.0.4 + 4.13.2 2.7.3 - 1.5.7 + 1.5.6 @@ -78,10 +82,35 @@ mybatis ${mybatis.version} + + org.mybatis.scripting + mybatis-freemarker + ${mybatis-freemarker.version} + true + + + org.mybatis.scripting + mybatis-velocity + ${mybatis-velocity.version} + true + + + org.mybatis.scripting + mybatis-thymeleaf + ${mybatis-thymeleaf.version} + true + org.slf4j slf4j-api ${slf4j.version} + true + + + ch.qos.logback + logback-classic + ${logback.version} + true @@ -91,12 +120,6 @@ ${junit.version} test - - ch.qos.logback - logback-classic - ${logback.version} - test - org.hsqldb hsqldb diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/pom.xml b/spring-boot-starter/mapper-spring-boot-autoconfigure/pom.xml index 7b9be7921..97e10a6ad 100644 --- a/spring-boot-starter/mapper-spring-boot-autoconfigure/pom.xml +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/pom.xml @@ -41,6 +41,22 @@ spring-boot-autoconfigure + + org.mybatis.scripting + mybatis-freemarker + true + + + org.mybatis.scripting + mybatis-velocity + true + + + org.mybatis.scripting + mybatis-thymeleaf + true + + tk.mybatis mapper-core diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MapperAutoConfiguration.java b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MapperAutoConfiguration.java index 344bbdd5d..b6c587bda 100644 --- a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MapperAutoConfiguration.java +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MapperAutoConfiguration.java @@ -1,35 +1,50 @@ -/** - * Copyright 2015-2018 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 - *

- * 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. +/* + * Copyright 2015-2022 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. */ package tk.mybatis.mapper.autoconfigure; +import java.beans.PropertyDescriptor; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.sql.DataSource; + import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.type.TypeHandler; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -54,35 +69,26 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import tk.mybatis.spring.annotation.BaseProperties; -import tk.mybatis.spring.mapper.ClassPathMapperScanner; import tk.mybatis.spring.mapper.MapperFactoryBean; -import tk.mybatis.spring.mapper.SpringBootBindUtil; - -import javax.sql.DataSource; -import java.util.Arrays; -import java.util.List; +import tk.mybatis.spring.mapper.MapperScannerConfigurer; /** - * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a - * {@link SqlSessionFactory} and a {@link SqlSessionTemplate}. - * - * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a - * configuration file is specified as a property, those will be considered, - * otherwise this auto-configuration will attempt to register mappers based on - * the interface definitions in or under the root auto-configuration package. + * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a {@link SqlSessionFactory} and a + * {@link SqlSessionTemplate}. If {@link tk.mybatis.spring.annotation.MapperScan} is used, or a configuration file is + * specified as a property, those will be considered, otherwise this auto-configuration will attempt to register mappers + * based on the interface definitions in or under the root auto-configuration package. * * @author Eddú Meléndez * @author Josh Long * @author Kazuki Shimizu * @author Eduardo Macarrón */ -@org.springframework.context.annotation.Configuration +@org.springframework.context.annotation.Configuration(proxyBeanMethods = false) @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) -@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureBefore(name = "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration") +@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class}) public class MapperAutoConfiguration implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(MapperAutoConfiguration.class); @@ -91,22 +97,31 @@ public class MapperAutoConfiguration implements InitializingBean { private final Interceptor[] interceptors; + private final TypeHandler[] typeHandlers; + + private final LanguageDriver[] languageDrivers; + private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List configurationCustomizers; - public MapperAutoConfiguration(MybatisProperties properties, - ObjectProvider interceptorsProvider, - ResourceLoader resourceLoader, - ObjectProvider databaseIdProvider, - ObjectProvider> configurationCustomizersProvider) { + private final List sqlSessionFactoryBeanCustomizers; + + public MapperAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider, + ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider, + ResourceLoader resourceLoader, ObjectProvider databaseIdProvider, + ObjectProvider> configurationCustomizersProvider, + ObjectProvider> sqlSessionFactoryBeanCustomizers) { this.properties = properties; this.interceptors = interceptorsProvider.getIfAvailable(); + this.typeHandlers = typeHandlersProvider.getIfAvailable(); + this.languageDrivers = languageDriversProvider.getIfAvailable(); this.resourceLoader = resourceLoader; this.databaseIdProvider = databaseIdProvider.getIfAvailable(); this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable(); + this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable(); } @Override @@ -117,8 +132,8 @@ public void afterPropertiesSet() { private void checkConfigFileExists() { if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); - Assert.state(resource.exists(), "Cannot find config location: " + resource - + " (please add config file or check your Mybatis configuration)"); + Assert.state(resource.exists(), + "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)"); } } @@ -127,7 +142,9 @@ private void checkConfigFileExists() { public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); - factory.setVfs(SpringBootVFS.class); + if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) { + factory.setVfs(SpringBootVFS.class); + } if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } @@ -150,18 +167,41 @@ public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Excepti if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } - if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { - factory.setMapperLocations(this.properties.resolveMapperLocations()); + if (!ObjectUtils.isEmpty(this.typeHandlers)) { + factory.setTypeHandlers(this.typeHandlers); } - + Resource[] mapperLocations = this.properties.resolveMapperLocations(); + if (!ObjectUtils.isEmpty(mapperLocations)) { + factory.setMapperLocations(mapperLocations); + } + Set factoryPropertyNames = Stream + .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName) + .collect(Collectors.toSet()); + Class defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); + if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { + // Need to mybatis-spring 2.0.2+ + factory.setScriptingLanguageDrivers(this.languageDrivers); + if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { + defaultLanguageDriver = this.languageDrivers[0].getClass(); + } + } + if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { + // Need to mybatis-spring 2.0.2+ + factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); + } + applySqlSessionFactoryBeanCustomizers(factory); return factory.getObject(); } private void applyConfiguration(SqlSessionFactoryBean factory) { - Configuration configuration = this.properties.getConfiguration(); - if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { + MybatisProperties.CoreConfiguration coreConfiguration = this.properties.getConfiguration(); + Configuration configuration = null; + if (coreConfiguration != null || !StringUtils.hasText(this.properties.getConfigLocation())) { configuration = new Configuration(); } + if (configuration != null && coreConfiguration != null) { + coreConfiguration.applyTo(configuration); + } if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { for (ConfigurationCustomizer customizer : this.configurationCustomizers) { customizer.customize(configuration); @@ -170,6 +210,14 @@ private void applyConfiguration(SqlSessionFactoryBean factory) { factory.setConfiguration(configuration); } + private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) { + if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) { + for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) { + customizer.customize(factory); + } + } + } + @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { @@ -182,91 +230,107 @@ public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory } /** - * This will just scan the same base package as Spring Boot does. If you want - * more power, you can explicitly use - * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed - * mappers working correctly, out-of-the-box, similar to using Spring Data JPA - * repositories. + * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use + * {@link tk.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box, + * similar to using Spring Data JPA repositories. */ public static class AutoConfiguredMapperScannerRegistrar - implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { + implements BeanFactoryAware, ResourceLoaderAware, EnvironmentAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; - private ResourceLoader resourceLoader; - private Environment environment; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (!AutoConfigurationPackages.has(this.beanFactory)) { + logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); + return; + } + logger.debug("Searching for mappers annotated with @Mapper"); - ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry, environment); - scanner.setMapperProperties(environment); - try { - if (this.resourceLoader != null) { - scanner.setResourceLoader(this.resourceLoader); - } - List packages = AutoConfigurationPackages.get(this.beanFactory); - if (logger.isDebugEnabled()) { - for (String pkg : packages) { - logger.debug("Using auto-configuration base package '{}'", pkg); - } - } - BaseProperties properties = SpringBootBindUtil.bind(environment, BaseProperties.class, BaseProperties.MYBATIS_PREFIX); - if (properties != null && properties.getBasePackages() != null && properties.getBasePackages().length > 0) { - packages.addAll(Arrays.asList(properties.getBasePackages())); - } else { - //设置了包名的情况下,不需要指定该注解 - scanner.setAnnotationClass(Mapper.class); - } + List packages = AutoConfigurationPackages.get(this.beanFactory); + if (logger.isDebugEnabled()) { + packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg)); + } - String lazyInitialization = environment.getProperty("mybatis.lazy-initialization"); - if (StringUtils.hasText(lazyInitialization)) { - scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); - } + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); + builder.addPropertyValue("processPropertyPlaceHolders", true); + builder.addPropertyValue("annotationClass", Mapper.class); + builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); + builder.addPropertyValue("mapperProperties", this.environment); + BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); + Set propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName) + .collect(Collectors.toSet()); + if (propertyNames.contains("lazyInitialization")) { + // Need to mybatis-spring 2.0.2+ + builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"); + } + if (propertyNames.contains("defaultScope")) { + // Need to mybatis-spring 2.0.6+ + builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}"); + } - scanner.registerFilters(); - scanner.doScan(StringUtils.toStringArray(packages)); - } catch (IllegalStateException ex) { - logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex); + // for spring-native + boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class, + Boolean.TRUE); + if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) { + ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory; + Optional sqlSessionTemplateBeanName = Optional + .ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory)); + Optional sqlSessionFactoryBeanName = Optional + .ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory)); + if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) { + builder.addPropertyValue("sqlSessionTemplateBeanName", + sqlSessionTemplateBeanName.orElse("sqlSessionTemplate")); + } else { + builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get()); + } } + builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + + registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + @Override public void setEnvironment(Environment environment) { this.environment = environment; } - @Override - public void setResourceLoader(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; + private String getBeanNameForType(Class type, ListableBeanFactory factory) { + String[] beanNames = factory.getBeanNamesForType(type); + return beanNames.length > 0 ? beanNames[0] : null; } + } /** - * {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up - * creating instances of {@link MapperFactoryBean}. If - * {@link org.mybatis.spring.annotation.MapperScan} is used then this - * auto-configuration is not needed. If it is _not_ used, however, then this - * will bring in a bean registrar and automatically register components based - * on the same component-scanning path as Spring Boot itself. + * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan + * mappers based on the same component-scanning path as Spring Boot itself. */ - @org.springframework.context.annotation.Configuration - @Import({AutoConfiguredMapperScannerRegistrar.class}) - @ConditionalOnMissingBean(MapperFactoryBean.class) + @org.springframework.context.annotation.Configuration(proxyBeanMethods = false) + @Import(AutoConfiguredMapperScannerRegistrar.class) + @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { @Override public void afterPropertiesSet() { - logger.debug("No {} found.", MapperFactoryBean.class.getName()); + logger.debug( + "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); } + } /** @@ -282,4 +346,5 @@ public MapperCacheDisabler mapperCacheDisabler() { } } + } diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisDependsOnDatabaseInitializationDetector.java b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisDependsOnDatabaseInitializationDetector.java new file mode 100644 index 000000000..26fa98b6d --- /dev/null +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisDependsOnDatabaseInitializationDetector.java @@ -0,0 +1,24 @@ +package tk.mybatis.mapper.autoconfigure; + +import java.util.Collections; +import java.util.Set; + +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector; +import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector; + +/** + * {@link DependsOnDatabaseInitializationDetector} for Mybatis. + * + * @author Eddú Meléndez + * @since 2.3.0 + */ +class MybatisDependsOnDatabaseInitializationDetector + extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector { + + @Override + protected Set> getDependsOnDatabaseInitializationBeanTypes() { + return Collections.singleton(SqlSessionTemplate.class); + } + +} diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisLanguageDriverAutoConfiguration.java b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisLanguageDriverAutoConfiguration.java new file mode 100644 index 000000000..85c2a245c --- /dev/null +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisLanguageDriverAutoConfiguration.java @@ -0,0 +1,136 @@ +package tk.mybatis.mapper.autoconfigure; + +import org.apache.ibatis.scripting.LanguageDriver; +import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver; +import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig; +import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver; +import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriverConfig; +import org.mybatis.scripting.velocity.VelocityLanguageDriver; +import org.mybatis.scripting.velocity.VelocityLanguageDriverConfig; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-Configuration} for MyBatis's scripting language drivers. + * + * @author Kazuki Shimizu + * @since 2.1.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(LanguageDriver.class) +public class MybatisLanguageDriverAutoConfiguration { + + private static final String CONFIGURATION_PROPERTY_PREFIX = "mybatis.scripting-language-driver"; + + /** + * Configuration class for mybatis-freemarker 1.1.x or under. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(FreeMarkerLanguageDriver.class) + @ConditionalOnMissingClass("org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig") + public static class LegacyFreeMarkerConfiguration { + @Bean + @ConditionalOnMissingBean + FreeMarkerLanguageDriver freeMarkerLanguageDriver() { + return new FreeMarkerLanguageDriver(); + } + } + + /** + * Configuration class for mybatis-freemarker 1.2.x or above. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({FreeMarkerLanguageDriver.class, FreeMarkerLanguageDriverConfig.class}) + public static class FreeMarkerConfiguration { + @Bean + @ConditionalOnMissingBean + FreeMarkerLanguageDriver freeMarkerLanguageDriver(FreeMarkerLanguageDriverConfig config) { + return new FreeMarkerLanguageDriver(config); + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties(CONFIGURATION_PROPERTY_PREFIX + ".freemarker") + public FreeMarkerLanguageDriverConfig freeMarkerLanguageDriverConfig() { + return FreeMarkerLanguageDriverConfig.newInstance(); + } + } + + /** + * Configuration class for mybatis-velocity 2.0 or under. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(org.mybatis.scripting.velocity.Driver.class) + @ConditionalOnMissingClass("org.mybatis.scripting.velocity.VelocityLanguageDriverConfig") + @SuppressWarnings("deprecation") + public static class LegacyVelocityConfiguration { + @Bean + @ConditionalOnMissingBean + org.mybatis.scripting.velocity.Driver velocityLanguageDriver() { + return new org.mybatis.scripting.velocity.Driver(); + } + } + + /** + * Configuration class for mybatis-velocity 2.1.x or above. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({VelocityLanguageDriver.class, VelocityLanguageDriverConfig.class}) + public static class VelocityConfiguration { + @Bean + @ConditionalOnMissingBean + VelocityLanguageDriver velocityLanguageDriver(VelocityLanguageDriverConfig config) { + return new VelocityLanguageDriver(config); + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties(CONFIGURATION_PROPERTY_PREFIX + ".velocity") + public VelocityLanguageDriverConfig velocityLanguageDriverConfig() { + return VelocityLanguageDriverConfig.newInstance(); + } + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ThymeleafLanguageDriver.class) + public static class ThymeleafConfiguration { + @Bean + @ConditionalOnMissingBean + ThymeleafLanguageDriver thymeleafLanguageDriver(ThymeleafLanguageDriverConfig config) { + return new ThymeleafLanguageDriver(config); + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties(CONFIGURATION_PROPERTY_PREFIX + ".thymeleaf") + public ThymeleafLanguageDriverConfig thymeleafLanguageDriverConfig() { + return ThymeleafLanguageDriverConfig.newInstance(); + } + + // This class provides to avoid the https://github.com/spring-projects/spring-boot/issues/21626 as workaround. + @SuppressWarnings("unused") + private static class MetadataThymeleafLanguageDriverConfig extends ThymeleafLanguageDriverConfig { + + @ConfigurationProperties(CONFIGURATION_PROPERTY_PREFIX + ".thymeleaf.dialect") + @Override + public DialectConfig getDialect() { + return super.getDialect(); + } + + @ConfigurationProperties(CONFIGURATION_PROPERTY_PREFIX + ".thymeleaf.template-file") + @Override + public TemplateFileConfig getTemplateFile() { + return super.getTemplateFile(); + } + + } + + } + +} + diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisProperties.java b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisProperties.java index 33130ffa6..fd6af07fa 100644 --- a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisProperties.java +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/MybatisProperties.java @@ -15,20 +15,29 @@ */ package tk.mybatis.mapper.autoconfigure; +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.mapping.ResultSetType; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.AutoMappingBehavior; +import org.apache.ibatis.session.AutoMappingUnknownColumnBehavior; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.LocalCacheScope; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.TypeHandler; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import tk.mybatis.spring.annotation.BaseProperties; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.Optional; import java.util.Properties; +import java.util.Set; +import java.util.stream.Stream; /** * Configuration properties for MyBatis. @@ -57,8 +66,8 @@ public class MybatisProperties extends BaseProperties { private String typeAliasesPackage; /** - * The super class for filtering type alias. - * If this not specifies, the MyBatis deal as type alias all classes that searched from typeAliasesPackage. + * The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that + * searched from typeAliasesPackage. */ private Class typeAliasesSuperType; @@ -77,17 +86,21 @@ public class MybatisProperties extends BaseProperties { */ private ExecutorType executorType; + /** + * The default scripting language driver class. (Available when use together with mybatis-spring 2.0.2+) + */ + private Class defaultScriptingLanguageDriver; + /** * Externalized properties for MyBatis configuration. */ private Properties configurationProperties; /** - * A Configuration object for customize default settings. If {@link #configLocation} - * is specified, this property is not used. + * A Configuration object for customize default settings. If {@link #configLocation} is specified, this property is + * not used. */ - @NestedConfigurationProperty - private Configuration configuration; + private CoreConfiguration configuration; /** * @since 1.1.0 @@ -103,16 +116,6 @@ public void setConfigLocation(String configLocation) { this.configLocation = configLocation; } - @Deprecated - public String getConfig() { - return this.configLocation; - } - - @Deprecated - public void setConfig(String config) { - this.configLocation = config; - } - public String[] getMapperLocations() { return this.mapperLocations; } @@ -167,6 +170,20 @@ public void setExecutorType(ExecutorType executorType) { this.executorType = executorType; } + /** + * @since 2.1.0 + */ + public Class getDefaultScriptingLanguageDriver() { + return defaultScriptingLanguageDriver; + } + + /** + * @since 2.1.0 + */ + public void setDefaultScriptingLanguageDriver(Class defaultScriptingLanguageDriver) { + this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver; + } + /** * @since 1.2.0 */ @@ -181,22 +198,17 @@ public void setConfigurationProperties(Properties configurationProperties) { this.configurationProperties = configurationProperties; } - public Configuration getConfiguration() { + public CoreConfiguration getConfiguration() { return configuration; } - public void setConfiguration(Configuration configuration) { + public void setConfiguration(CoreConfiguration configuration) { this.configuration = configuration; } public Resource[] resolveMapperLocations() { - List resources = new ArrayList(); - if (this.mapperLocations != null) { - for (String mapperLocation : this.mapperLocations) { - resources.addAll(Arrays.asList(getResources(mapperLocation))); - } - } - return resources.toArray(new Resource[resources.size()]); + return Stream.of(Optional.ofNullable(this.mapperLocations).orElse(new String[0])) + .flatMap(location -> Stream.of(getResources(location))).toArray(Resource[]::new); } private Resource[] getResources(String location) { @@ -207,4 +219,463 @@ private Resource[] getResources(String location) { } } + /** + * The configuration properties for mybatis core module. + * + * @since 3.0.0 + */ + public static class CoreConfiguration { + + /** + * Specifies the TypeHandler used by default for Enum. + */ + Class defaultEnumTypeHandler; + /** + * Allows using RowBounds on nested statements. If allow, set the false. Default is false. + */ + private Boolean safeRowBoundsEnabled; + /** + * Allows using ResultHandler on nested statements. If allow, set the false. Default is true. + */ + private Boolean safeResultHandlerEnabled; + /** + * Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names + * aColumn. Default is false. + */ + private Boolean mapUnderscoreToCamelCase; + /** + * When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded + * on demand (see also lazyLoadTriggerMethods). Default is false. + */ + private Boolean aggressiveLazyLoading; + /** + * Allows or disallows multiple ResultSets to be returned from a single statement (compatible driver required). + * Default is true. + */ + private Boolean multipleResultSetsEnabled; + /** + * Allows JDBC support for generated keys. A compatible driver is required. This setting forces generated keys to be + * used if set to true, as some drivers deny compatibility but still work (e.g. Derby). Default is false. + */ + private Boolean useGeneratedKeys; + /** + * Uses the column label instead of the column name. Different drivers behave differently in this respect. Refer to + * the driver documentation, or test out both modes to determine how your driver behaves. Default is true. + */ + private Boolean useColumnLabel; + /** + * Globally enables or disables any caches configured in any mapper under this configuration. Default is true. + */ + private Boolean cacheEnabled; + /** + * Specifies if setters or map's put method will be called when a retrieved value is null. It is useful when you + * rely on Map.keySet() or null value initialization. Note primitives such as (int,boolean,etc.) will not be set to + * null. Default is false. + */ + private Boolean callSettersOnNulls; + /** + * Allow referencing statement parameters by their actual names declared in the method signature. To use this + * feature, your project must be compiled in Java 8 with -parameters option. Default is true. + */ + private Boolean useActualParamName; + /** + * MyBatis, by default, returns null when all the columns of a returned row are NULL. When this setting is enabled, + * MyBatis returns an empty instance instead. Note that it is also applied to nested results (i.e. collectioin and + * association). Default is false. + */ + private Boolean returnInstanceForEmptyRow; + /** + * Removes extra whitespace characters from the SQL. Note that this also affects literal strings in SQL. Default is + * false. + */ + private Boolean shrinkWhitespacesInSql; + /** + * Specifies the default value of 'nullable' attribute on 'foreach' tag. Default is false. + */ + private Boolean nullableOnForEach; + /** + * When applying constructor auto-mapping, argument name is used to search the column to map instead of relying on + * the column order. Default is false. + */ + private Boolean argNameBasedConstructorAutoMapping; + /** + * Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. This value can be + * superseded for a specific relation by using the fetchType attribute on it. Default is False. + */ + private Boolean lazyLoadingEnabled; + /** + * Sets the number of seconds the driver will wait for a response from the database. + */ + private Integer defaultStatementTimeout; + /** + * Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a + * query setting. + */ + private Integer defaultFetchSize; + /** + * MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default + * (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be + * used just for statement execution, no data will be shared between two different calls to the same SqlSession. + * Default is SESSION. + */ + private LocalCacheScope localCacheScope; + /** + * Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. Some drivers + * require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER. Default + * is OTHER. + */ + private JdbcType jdbcTypeForNull; + /** + * Specifies a scroll strategy when omit it per statement settings. + */ + private ResultSetType defaultResultSetType; + /** + * Configures the default executor. SIMPLE executor does nothing special. REUSE executor reuses prepared statements. + * BATCH executor reuses statements and batches updates. Default is SIMPLE. + */ + private ExecutorType defaultExecutorType; + /** + * Specifies if and how MyBatis should automatically map columns to fields/properties. NONE disables auto-mapping. + * PARTIAL will only auto-map results with no nested result mappings defined inside. FULL will auto-map result + * mappings of any complexity (containing nested or otherwise). Default is PARTIAL. + */ + private AutoMappingBehavior autoMappingBehavior; + /** + * Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target. + * Default is NONE. + */ + private AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior; + /** + * Specifies the prefix string that MyBatis will add to the logger names. + */ + private String logPrefix; + /** + * Specifies which Object's methods trigger a lazy load. Default is [equals,clone,hashCode,toString]. + */ + private Set lazyLoadTriggerMethods; + /** + * Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation + * will be autodiscovered. + */ + private Class logImpl; + /** + * Specifies VFS implementations. + */ + private Class vfsImpl; + /** + * Specifies an sql provider class that holds provider method. This class apply to the type(or value) attribute on + * sql provider annotation(e.g. @SelectProvider), when these attribute was omitted. + */ + private Class defaultSqlProviderType; + /** + * Specifies the class that provides an instance of Configuration. The returned Configuration instance is used to + * load lazy properties of deserialized objects. This class must have a method with a signature static Configuration + * getConfiguration(). + */ + private Class configurationFactory; + + /** + * Specify any configuration variables. + */ + private Properties variables; + + /** + * Specifies the database identify value for switching query to use. + */ + private String databaseId; + + public Boolean getSafeRowBoundsEnabled() { + return safeRowBoundsEnabled; + } + + public void setSafeRowBoundsEnabled(Boolean safeRowBoundsEnabled) { + this.safeRowBoundsEnabled = safeRowBoundsEnabled; + } + + public Boolean getSafeResultHandlerEnabled() { + return safeResultHandlerEnabled; + } + + public void setSafeResultHandlerEnabled(Boolean safeResultHandlerEnabled) { + this.safeResultHandlerEnabled = safeResultHandlerEnabled; + } + + public Boolean getMapUnderscoreToCamelCase() { + return mapUnderscoreToCamelCase; + } + + public void setMapUnderscoreToCamelCase(Boolean mapUnderscoreToCamelCase) { + this.mapUnderscoreToCamelCase = mapUnderscoreToCamelCase; + } + + public Boolean getAggressiveLazyLoading() { + return aggressiveLazyLoading; + } + + public void setAggressiveLazyLoading(Boolean aggressiveLazyLoading) { + this.aggressiveLazyLoading = aggressiveLazyLoading; + } + + public Boolean getMultipleResultSetsEnabled() { + return multipleResultSetsEnabled; + } + + public void setMultipleResultSetsEnabled(Boolean multipleResultSetsEnabled) { + this.multipleResultSetsEnabled = multipleResultSetsEnabled; + } + + public Boolean getUseGeneratedKeys() { + return useGeneratedKeys; + } + + public void setUseGeneratedKeys(Boolean useGeneratedKeys) { + this.useGeneratedKeys = useGeneratedKeys; + } + + public Boolean getUseColumnLabel() { + return useColumnLabel; + } + + public void setUseColumnLabel(Boolean useColumnLabel) { + this.useColumnLabel = useColumnLabel; + } + + public Boolean getCacheEnabled() { + return cacheEnabled; + } + + public void setCacheEnabled(Boolean cacheEnabled) { + this.cacheEnabled = cacheEnabled; + } + + public Boolean getCallSettersOnNulls() { + return callSettersOnNulls; + } + + public void setCallSettersOnNulls(Boolean callSettersOnNulls) { + this.callSettersOnNulls = callSettersOnNulls; + } + + public Boolean getUseActualParamName() { + return useActualParamName; + } + + public void setUseActualParamName(Boolean useActualParamName) { + this.useActualParamName = useActualParamName; + } + + public Boolean getReturnInstanceForEmptyRow() { + return returnInstanceForEmptyRow; + } + + public void setReturnInstanceForEmptyRow(Boolean returnInstanceForEmptyRow) { + this.returnInstanceForEmptyRow = returnInstanceForEmptyRow; + } + + public Boolean getShrinkWhitespacesInSql() { + return shrinkWhitespacesInSql; + } + + public void setShrinkWhitespacesInSql(Boolean shrinkWhitespacesInSql) { + this.shrinkWhitespacesInSql = shrinkWhitespacesInSql; + } + + public Boolean getNullableOnForEach() { + return nullableOnForEach; + } + + public void setNullableOnForEach(Boolean nullableOnForEach) { + this.nullableOnForEach = nullableOnForEach; + } + + public Boolean getArgNameBasedConstructorAutoMapping() { + return argNameBasedConstructorAutoMapping; + } + + public void setArgNameBasedConstructorAutoMapping(Boolean argNameBasedConstructorAutoMapping) { + this.argNameBasedConstructorAutoMapping = argNameBasedConstructorAutoMapping; + } + + public String getLogPrefix() { + return logPrefix; + } + + public void setLogPrefix(String logPrefix) { + this.logPrefix = logPrefix; + } + + public Class getLogImpl() { + return logImpl; + } + + public void setLogImpl(Class logImpl) { + this.logImpl = logImpl; + } + + public Class getVfsImpl() { + return vfsImpl; + } + + public void setVfsImpl(Class vfsImpl) { + this.vfsImpl = vfsImpl; + } + + public Class getDefaultSqlProviderType() { + return defaultSqlProviderType; + } + + public void setDefaultSqlProviderType(Class defaultSqlProviderType) { + this.defaultSqlProviderType = defaultSqlProviderType; + } + + public LocalCacheScope getLocalCacheScope() { + return localCacheScope; + } + + public void setLocalCacheScope(LocalCacheScope localCacheScope) { + this.localCacheScope = localCacheScope; + } + + public JdbcType getJdbcTypeForNull() { + return jdbcTypeForNull; + } + + public void setJdbcTypeForNull(JdbcType jdbcTypeForNull) { + this.jdbcTypeForNull = jdbcTypeForNull; + } + + public Set getLazyLoadTriggerMethods() { + return lazyLoadTriggerMethods; + } + + public void setLazyLoadTriggerMethods(Set lazyLoadTriggerMethods) { + this.lazyLoadTriggerMethods = lazyLoadTriggerMethods; + } + + public Integer getDefaultStatementTimeout() { + return defaultStatementTimeout; + } + + public void setDefaultStatementTimeout(Integer defaultStatementTimeout) { + this.defaultStatementTimeout = defaultStatementTimeout; + } + + public Integer getDefaultFetchSize() { + return defaultFetchSize; + } + + public void setDefaultFetchSize(Integer defaultFetchSize) { + this.defaultFetchSize = defaultFetchSize; + } + + public ResultSetType getDefaultResultSetType() { + return defaultResultSetType; + } + + public void setDefaultResultSetType(ResultSetType defaultResultSetType) { + this.defaultResultSetType = defaultResultSetType; + } + + public ExecutorType getDefaultExecutorType() { + return defaultExecutorType; + } + + public void setDefaultExecutorType(ExecutorType defaultExecutorType) { + this.defaultExecutorType = defaultExecutorType; + } + + public AutoMappingBehavior getAutoMappingBehavior() { + return autoMappingBehavior; + } + + public void setAutoMappingBehavior(AutoMappingBehavior autoMappingBehavior) { + this.autoMappingBehavior = autoMappingBehavior; + } + + public AutoMappingUnknownColumnBehavior getAutoMappingUnknownColumnBehavior() { + return autoMappingUnknownColumnBehavior; + } + + public void setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior) { + this.autoMappingUnknownColumnBehavior = autoMappingUnknownColumnBehavior; + } + + public Properties getVariables() { + return variables; + } + + public void setVariables(Properties variables) { + this.variables = variables; + } + + public Boolean getLazyLoadingEnabled() { + return lazyLoadingEnabled; + } + + public void setLazyLoadingEnabled(Boolean lazyLoadingEnabled) { + this.lazyLoadingEnabled = lazyLoadingEnabled; + } + + public Class getConfigurationFactory() { + return configurationFactory; + } + + public void setConfigurationFactory(Class configurationFactory) { + this.configurationFactory = configurationFactory; + } + + public Class getDefaultEnumTypeHandler() { + return defaultEnumTypeHandler; + } + + public void setDefaultEnumTypeHandler(Class defaultEnumTypeHandler) { + this.defaultEnumTypeHandler = defaultEnumTypeHandler; + } + + public String getDatabaseId() { + return databaseId; + } + + public void setDatabaseId(String databaseId) { + this.databaseId = databaseId; + } + + public void applyTo(Configuration target) { + PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); + mapper.from(getSafeRowBoundsEnabled()).to(target::setSafeRowBoundsEnabled); + mapper.from(getSafeResultHandlerEnabled()).to(target::setSafeResultHandlerEnabled); + mapper.from(getMapUnderscoreToCamelCase()).to(target::setMapUnderscoreToCamelCase); + mapper.from(getAggressiveLazyLoading()).to(target::setAggressiveLazyLoading); + mapper.from(getMultipleResultSetsEnabled()).to(target::setMultipleResultSetsEnabled); + mapper.from(getUseGeneratedKeys()).to(target::setUseGeneratedKeys); + mapper.from(getUseColumnLabel()).to(target::setUseColumnLabel); + mapper.from(getCacheEnabled()).to(target::setCacheEnabled); + mapper.from(getCallSettersOnNulls()).to(target::setCallSettersOnNulls); + mapper.from(getUseActualParamName()).to(target::setUseActualParamName); + mapper.from(getReturnInstanceForEmptyRow()).to(target::setReturnInstanceForEmptyRow); + mapper.from(getShrinkWhitespacesInSql()).to(target::setShrinkWhitespacesInSql); + mapper.from(getNullableOnForEach()).to(target::setNullableOnForEach); + mapper.from(getArgNameBasedConstructorAutoMapping()).to(target::setArgNameBasedConstructorAutoMapping); + mapper.from(getLazyLoadingEnabled()).to(target::setLazyLoadingEnabled); + mapper.from(getLogPrefix()).to(target::setLogPrefix); + mapper.from(getLazyLoadTriggerMethods()).to(target::setLazyLoadTriggerMethods); + mapper.from(getDefaultStatementTimeout()).to(target::setDefaultStatementTimeout); + mapper.from(getDefaultFetchSize()).to(target::setDefaultFetchSize); + mapper.from(getLocalCacheScope()).to(target::setLocalCacheScope); + mapper.from(getJdbcTypeForNull()).to(target::setJdbcTypeForNull); + mapper.from(getDefaultResultSetType()).to(target::setDefaultResultSetType); + mapper.from(getDefaultExecutorType()).to(target::setDefaultExecutorType); + mapper.from(getAutoMappingBehavior()).to(target::setAutoMappingBehavior); + mapper.from(getAutoMappingUnknownColumnBehavior()).to(target::setAutoMappingUnknownColumnBehavior); + mapper.from(getVariables()).to(target::setVariables); + mapper.from(getLogImpl()).to(target::setLogImpl); + mapper.from(getVfsImpl()).to(target::setVfsImpl); + mapper.from(getDefaultSqlProviderType()).to(target::setDefaultSqlProviderType); + mapper.from(getConfigurationFactory()).to(target::setConfigurationFactory); + mapper.from(getDefaultEnumTypeHandler()).to(target::setDefaultEnumTypeHandler); + mapper.from(getDatabaseId()).to(target::setDatabaseId); + } + + } + } diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SpringBootVFS.java b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SpringBootVFS.java index 66d0b77b0..e68354615 100644 --- a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SpringBootVFS.java +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SpringBootVFS.java @@ -15,16 +15,22 @@ */ package tk.mybatis.mapper.autoconfigure; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.text.Normalizer; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.apache.ibatis.io.VFS; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; - -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; +import org.springframework.util.ClassUtils; /** * @author Hans Westerbeek @@ -33,10 +39,17 @@ */ public class SpringBootVFS extends VFS { + private static Charset urlDecodingCharset; + private static Supplier classLoaderSupplier; private final ResourcePatternResolver resourceResolver; + static { + setUrlDecodingCharset(Charset.defaultCharset()); + setClassLoaderSupplier(ClassUtils::getDefaultClassLoader); + } + public SpringBootVFS() { - this.resourceResolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader()); + this.resourceResolver = new PathMatchingResourcePatternResolver(classLoaderSupplier.get()); } @Override @@ -44,20 +57,51 @@ public boolean isValid() { return true; } - @Override - protected List list(URL url, String path) throws IOException { - Resource[] resources = resourceResolver.getResources("classpath*:" + path + "/**/*.class"); - List resourcePaths = new ArrayList(); - for (Resource resource : resources) { - resourcePaths.add(preserveSubpackageName(resource.getURI(), path)); + /** + * Set the charset for decoding an encoded URL string. + *

+ * Default is system default charset. + *

+ * + * @param charset the charset for decoding an encoded URL string + * @since 2.3.0 + */ + public static void setUrlDecodingCharset(Charset charset) { + urlDecodingCharset = charset; + } + + /** + * Set the supplier for providing {@link ClassLoader} to used. + *

+ * Default is a returned instance from {@link ClassUtils#getDefaultClassLoader()}. + *

+ * + * @param supplier the supplier for providing {@link ClassLoader} to used + * @since 3.0.2 + */ + public static void setClassLoaderSupplier(Supplier supplier) { + classLoaderSupplier = supplier; + } + + private static String preserveSubpackageName(final String baseUrlString, final Resource resource, + final String rootPath) { + try { + return rootPath + (rootPath.endsWith("/") ? "" : "/") + + Normalizer + .normalize(URLDecoder.decode(resource.getURL().toString(), urlDecodingCharset), Normalizer.Form.NFC) + .substring(baseUrlString.length()); + } catch (IOException e) { + throw new UncheckedIOException(e); } - return resourcePaths; } - private static String preserveSubpackageName(final URI uri, final String rootPath) { - final String uriStr = uri.toString(); - final int start = uriStr.indexOf(rootPath); - return uriStr.substring(start); + @Override + protected List list(URL url, String path) throws IOException { + String urlString = URLDecoder.decode(url.toString(), urlDecodingCharset); + String baseUrlString = urlString.endsWith("/") ? urlString : urlString.concat("/"); + Resource[] resources = resourceResolver.getResources(baseUrlString + "**/*.class"); + return Stream.of(resources).map(resource -> preserveSubpackageName(baseUrlString, resource, path)) + .collect(Collectors.toList()); } } diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SqlSessionFactoryBeanCustomizer.java b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SqlSessionFactoryBeanCustomizer.java new file mode 100644 index 000000000..2a3111460 --- /dev/null +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/java/tk/mybatis/mapper/autoconfigure/SqlSessionFactoryBeanCustomizer.java @@ -0,0 +1,21 @@ +package tk.mybatis.mapper.autoconfigure; + +import org.mybatis.spring.SqlSessionFactoryBean; + +/** + * Callback interface that can be customized a {@link SqlSessionFactoryBean} object generated on auto-configuration. + * + * @author Kazuki Shimizu + * @since 2.2.2 + */ +@FunctionalInterface +public interface SqlSessionFactoryBeanCustomizer { + + /** + * Customize the given a {@link SqlSessionFactoryBean} object. + * + * @param factoryBean the factory bean object to customize + */ + void customize(SqlSessionFactoryBean factoryBean); + +} diff --git a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 48243cbf6..e62a7c18c 100644 --- a/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-starter/mapper-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ +tk.mybatis.mapper.autoconfigure.MybatisLanguageDriverAutoConfiguration tk.mybatis.mapper.autoconfigure.MapperAutoConfiguration \ No newline at end of file diff --git a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/application.properties b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/application.properties index dca3cf5cf..5613106ea 100644 --- a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/application.properties +++ b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/application.properties @@ -23,4 +23,8 @@ # spring.sql.init.schema-locations=classpath:import.sql logging.level.root=WARN -logging.level.tk.mybatis.sample.mapper=TRACE \ No newline at end of file +logging.level.tk.mybatis.sample.mapper=TRACE +mapper.useSimpleType=false +mapper.enable-method-annotation=true +mapper.style=uppercase +mybatis.configuration.logImpl=org.apache.ibatis.logging.slf4j.Slf4jImpl \ No newline at end of file diff --git a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/logback.xml b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/logback.xml new file mode 100644 index 000000000..cbc7d3ef0 --- /dev/null +++ b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-annotation/src/main/resources/logback.xml @@ -0,0 +1,10 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + \ No newline at end of file diff --git a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/java/tk/mybatis/sample/SampleXmlApplication.java b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/java/tk/mybatis/sample/SampleXmlApplication.java index 57fdf5de2..1592b777b 100644 --- a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/java/tk/mybatis/sample/SampleXmlApplication.java +++ b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/java/tk/mybatis/sample/SampleXmlApplication.java @@ -24,12 +24,14 @@ package tk.mybatis.sample; +import org.apache.ibatis.annotations.Mapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import tk.mybatis.sample.domain.Country; import tk.mybatis.sample.mapper.CountryMapper; +import tk.mybatis.spring.annotation.MapperScan; import java.util.List; diff --git a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/application.yml b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/application.yml index 31048949b..aac52a11e 100644 --- a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/application.yml +++ b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/application.yml @@ -28,6 +28,8 @@ spring: mybatis: config-location: mybatis-config.xml base-packages: tk.mybatis.sample.mapper + mapper-locations: + - classpath: /tk/mybatis/sample/mapper logging: level: diff --git a/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/logback.xml b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/logback.xml new file mode 100644 index 000000000..cbc7d3ef0 --- /dev/null +++ b/spring-boot-starter/mapper-spring-boot-samples/mapper-spring-boot-sample-xml/src/main/resources/logback.xml @@ -0,0 +1,10 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + \ No newline at end of file diff --git a/spring/src/main/java/tk/mybatis/spring/annotation/MapperScans.java b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScans.java index b4b3bac80..1528e3b9d 100644 --- a/spring/src/main/java/tk/mybatis/spring/annotation/MapperScans.java +++ b/spring/src/main/java/tk/mybatis/spring/annotation/MapperScans.java @@ -20,17 +20,17 @@ import java.lang.annotation.*; /** - * The Container annotation that aggregates several {@link org.mybatis.spring.annotation.MapperScan} annotations. + * The Container annotation that aggregates several {@link tk.mybatis.spring.annotation.MapperScan} annotations. *

- * Can be used natively, declaring several nested {@link org.mybatis.spring.annotation.MapperScan} annotations. Can also be used in conjunction with - * Java 8's support for repeatable annotations, where {@link org.mybatis.spring.annotation.MapperScan} can simply be declared several times on the + * Can be used natively, declaring several nested {@link tk.mybatis.spring.annotation.MapperScan} annotations. Can also be used in conjunction with + * Java 8's support for repeatable annotations, where {@link tk.mybatis.spring.annotation.MapperScan} can simply be declared several times on the * same method, implicitly generating this container annotation. * * @author Kazuki Shimizu * * @since 2.0.0 * - * @see org.mybatis.spring.annotation.MapperScan + * @see tk.mybatis.spring.annotation.MapperScan */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)