All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.myoss.phoenix.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018-2018 https://github.com/myoss
 *
 * 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.
 *
 */

package com.github.myoss.phoenix.mybatis.spring.boot.autoconfigure;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Stream;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import com.github.myoss.phoenix.mybatis.mapper.register.MapperInterfaceRegister;
import com.github.myoss.phoenix.mybatis.plugin.ParameterHandlerCustomizer;
import com.github.myoss.phoenix.mybatis.plugin.ParameterHandlerInterceptor;
import com.github.myoss.phoenix.mybatis.spring.boot.autoconfigure.MybatisProperties.MapperScanner;
import com.github.myoss.phoenix.mybatis.spring.mapper.ClassPathMapperScanner;
import com.github.myoss.phoenix.mybatis.spring.mapper.MapperScannerConfigurer;
import com.github.myoss.phoenix.mybatis.table.Sequence;
import com.github.myoss.phoenix.mybatis.table.TableConfig;
import com.github.myoss.phoenix.mybatis.table.TableMetaObject;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * MyBatis Spring Boot项目自动配置
 *
 * @author Jerry.Chen
 * @since 2018年4月23日 上午11:07:07
 */
@Slf4j
@EnableConfigurationProperties({ MybatisProperties.class })
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@Configuration
public class MybatisAutoConfiguration {
    /**
     * MyBatis Spring Boot项目配置属性
     */
    private final MybatisProperties             properties;
    /**
     * Mybatis Interceptor Spring Bean【可选】
     */
    private final Interceptor[]                 interceptors;
    /**
     * 自定义配置 Spring Bean【可选】
     */
    private final List configurationCustomizers;
    /**
     * Spring Application Context
     */
    private final ApplicationContext            applicationContext;
    /**
     * resource loader
     */
    private final ResourceLoader                resourceLoader;

    /**
     * 初始化 MyBatis Spring Boot 项目自动配置
     *
     * @param properties MyBatis Spring Boot项目配置属性
     * @param interceptorsProvider Mybatis Interceptor Spring Bean【可选】
     * @param configurationCustomizersProvider 自定义配置 Spring Bean【可选】
     * @param applicationContext Spring Application Context
     * @param resourceLoader resource loader
     */
    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider,
                                    ObjectProvider> configurationCustomizersProvider,
                                    ApplicationContext applicationContext, ResourceLoader resourceLoader) {
        this.properties = properties;
        if (this.properties.getTableConfig() == null) {
            log.debug("MybatisProperties.tableConfig not config, initialize default");
            this.properties.setTableConfig(new TableConfig());
        }
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
        this.applicationContext = applicationContext;
    }

    /**
     * 检查配置文件是否存在。当前对象被 Spring 创建之后,会调用此方法。
     */
    @PostConstruct
    public 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)");
        }

        // 获取所有的 Sequence Bean,用于后面初始化 TableSequence
        Map beanMap = this.applicationContext.getBeansOfType(Sequence.class);
        if (!CollectionUtils.isEmpty(beanMap)) {
            for (Entry entry : beanMap.entrySet()) {
                TableMetaObject.addSequenceBean(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * 初始化 SqlSessionFactory
     *
     * @param dataSource 数据源
     * @return SqlSessionFactory 实例对象
     * @throws Exception 异常信息
     */
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = createSqlSessionFactoryBean(this.applicationContext, this.resourceLoader,
                dataSource, this.properties, this.configurationCustomizers, this.interceptors);
        return factory.getObject();
    }

    /**
     * 创建 SqlSessionFactory
     *
     * @param applicationContext 自定义配置 Spring Bean【可选】
     * @param resourceLoader resource loader
     * @param dataSource 数据源
     * @param properties MyBatis Spring Boot项目配置属性
     * @param configurationCustomizers 自定义配置 Spring Bean【可选】
     * @param interceptors Mybatis Interceptor Spring Bean【可选】
     * @return SqlSessionFactory 实例对象
     */
    public static SqlSessionFactoryBean createSqlSessionFactoryBean(ApplicationContext applicationContext,
                                                                    ResourceLoader resourceLoader,
                                                                    DataSource dataSource, MybatisProperties properties,
                                                                    List configurationCustomizers,
                                                                    Interceptor[] interceptors) {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(properties.getConfigLocation())) {
            factory.setConfigLocation(resourceLoader.getResource(properties.getConfigLocation()));
        }
        applyConfiguration(factory, properties, configurationCustomizers);
        if (properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(properties.getConfigurationProperties());
        }
        applyPlugins(applicationContext, factory, interceptors);
        Map databaseIdProviderMap = applicationContext
                .getBeansOfType(DatabaseIdProvider.class);
        if (!databaseIdProviderMap.isEmpty()) {
            DatabaseIdProvider databaseIdProvider = databaseIdProviderMap.values().iterator().next();
            factory.setDatabaseIdProvider(databaseIdProvider);
        }
        if (StringUtils.hasLength(properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(properties.getTypeAliasesPackage());
        }
        if (StringUtils.hasLength(properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(properties.getTypeHandlersPackage());
        }
        Resource[] mapperLocations = properties.resolveMapperLocations();
        if (!ObjectUtils.isEmpty(mapperLocations)) {
            factory.setMapperLocations(mapperLocations);
        }
        return factory;
    }

    /**
     * 为 SqlSessionFactory 配置 Mybatis 拦截器插件
     *
     * @param applicationContext 自定义配置 Spring Bean【可选】
     * @param factory SqlSessionFactory 实例对象
     * @param interceptors Mybatis Interceptor 实例对象【可选】
     */
    public static void applyPlugins(ApplicationContext applicationContext, SqlSessionFactoryBean factory,
                                    Interceptor[] interceptors) {
        Map beanMap = applicationContext
                .getBeansOfType(ParameterHandlerCustomizer.class);
        boolean emptyInterceptors = ObjectUtils.isEmpty(interceptors);
        if (CollectionUtils.isEmpty(beanMap)) {
            if (!emptyInterceptors) {
                // 如果没有 ParameterHandlerCustomizer Bean对象,则直接设置
                factory.setPlugins(interceptors);
            }
            return;
        }

        // 有 ParameterHandlerCustomizer Bean对象
        ParameterHandlerCustomizer parameterHandlerCustomizer = beanMap.entrySet().iterator().next().getValue();
        if (emptyInterceptors) {
            // 没有 interceptors ,则创建新的 ParameterHandlerInterceptor
            ParameterHandlerInterceptor interceptor = new ParameterHandlerInterceptor(parameterHandlerCustomizer);
            factory.setPlugins(new Interceptor[] { interceptor });
            return;
        }

        if (Stream.of(interceptors).anyMatch(s -> s instanceof ParameterHandlerInterceptor)) {
            // 已经有 ParameterHandlerInterceptor,则不处理
            factory.setPlugins(interceptors);
        } else {
            // 添加新的 ParameterHandlerInterceptor
            ParameterHandlerInterceptor interceptor = new ParameterHandlerInterceptor(parameterHandlerCustomizer);
            Interceptor[] plugins = ArrayUtils.add(interceptors, interceptor);
            factory.setPlugins(plugins);
        }
    }

    /**
     * 为 SqlSessionFactory 配置"自定义配置"插件
     *
     * @param factory SqlSessionFactory 实例对象
     * @param properties MyBatis Spring Boot项目配置属性
     * @param configurationCustomizers 自定义配置【可选】
     */
    public static void applyConfiguration(SqlSessionFactoryBean factory, MybatisProperties properties,
                                          List configurationCustomizers) {
        org.apache.ibatis.session.Configuration configuration = properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(properties.getConfigLocation())) {
            configuration = new org.apache.ibatis.session.Configuration();
        }
        if (configuration != null && !CollectionUtils.isEmpty(configurationCustomizers)) {
            for (ConfigurationCustomizer customizer : configurationCustomizers) {
                customizer.customize(configuration);
            }
        }
        factory.setConfiguration(configuration);
    }

    /**
     * 初始化 SqlSessionTemplate
     *
     * @param sqlSessionFactory SqlSessionFactory 实例对象
     * @return SqlSessionTemplate 实例对象
     */
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return createSqlSessionTemplate(sqlSessionFactory, this.properties);
    }

    /**
     * 创建 SqlSessionTemplate
     *
     * @param sqlSessionFactory SqlSessionFactory 实例对象
     * @param properties MyBatis Spring Boot项目配置属性
     * @return SqlSessionTemplate 实例对象
     */
    public static SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory,
                                                              MybatisProperties properties) {
        ExecutorType executorType = properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

    /**
     * 初始化通用 Mapper 接口注册器
     *
     * @return MapperInterfaceRegister 实例对象
     */
    @Bean
    @ConditionalOnMissingBean
    public MapperInterfaceRegister mapperInterfaceRegister() {
        return new MapperInterfaceRegister(this.properties.getTableConfig());
    }

    /**
     * {@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.
     */
    @ConditionalOnMissingBean(MapperFactoryBean.class)
    @Import({ AutoConfiguredMapperScannerRegistrar.class })
    @Configuration
    public static class MapperScannerRegistrarNotFoundConfiguration {

        /**
         * 属性配置
         */
        @PostConstruct
        public void afterPropertiesSet() {
            log.debug("No {} found.", MapperFactoryBean.class.getName());
        }

    }

    /**
     * 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.
     */
    public static class AutoConfiguredMapperScannerRegistrar
            implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
        @Setter
        private BeanFactory    beanFactory;
        @Setter
        private ResourceLoader resourceLoader;

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            log.debug("Searching for mappers annotated with @Mapper");
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
            try {
                if (this.resourceLoader != null) {
                    scanner.setResourceLoader(this.resourceLoader);
                }

                List packages = AutoConfigurationPackages.get(this.beanFactory);
                if (log.isDebugEnabled()) {
                    packages.forEach(pkg -> log.debug("Using auto-configuration base package '{}'", pkg));
                }

                // 暂时不启用,自动注入: {@link MybatisAutoConfiguration#mapperInterfaceRegister()}
                //                // 通用Mapper接口注册器
                //                Binder propertySourcesBinder = Binder.get(environment);
                //                MybatisProperties mybatisProperties = propertySourcesBinder.bind(MybatisProperties.MYBATIS_PREFIX,
                //                        MybatisProperties.class).get();
                //                MapperInterfaceRegister mapperInterfaceRegister = new MapperInterfaceRegister(
                //                        mybatisProperties.getTableConfig());
                //                scanner.setMapperInterfaceRegister(mapperInterfaceRegister);

                scanner.setAnnotationClass(Mapper.class);
                scanner.registerFilters();
                scanner.doScan(StringUtils.toStringArray(packages));
            } catch (IllegalStateException ex) {
                log.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
            }
        }
    }

    /**
     * 自动扫描 Mapper Interface
     *
     * @see MapperScannerConfigurer
     */
    @ConditionalOnExpression("#{'${" + MybatisProperties.MYBATIS_MAPPER_SCANNER_PREFIX
            + ".base-package:}'.length() > 0}")
    @Configuration
    public static class AutoConfiguredMapperScannerRegistrar2 {
        /**
         * 初始化自动扫描 Mapper Interface
         */
        public AutoConfiguredMapperScannerRegistrar2() {
            log.debug(MybatisProperties.MYBATIS_MAPPER_SCANNER_PREFIX + ".base-package} is config [{}].",
                    AutoConfiguredMapperScannerRegistrar2.class.getName());
        }

        /**
         * 初始化 MapperScannerConfigurer
         *
         * @param environment Spring environment
         * @return MapperScannerConfigurer 实例对象
         */
        @ConditionalOnMissingBean
        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer(Environment environment) {
            Binder binder = Binder.get(environment);
            MapperScanner scanner = binder.bind(MybatisProperties.MYBATIS_MAPPER_SCANNER_PREFIX, MapperScanner.class)
                    .get();
            String basePackage = scanner.getBasePackage();
            Objects.requireNonNull(basePackage, "Property 'basePackage' is required");

            MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
            mapperScannerConfigurer.setBasePackage(basePackage);
            mapperScannerConfigurer.setSqlSessionFactoryBeanName(scanner.getSqlSessionFactoryName());
            mapperScannerConfigurer.setSqlSessionTemplateBeanName(scanner.getSqlSessionTemplateBeanName());
            if (scanner.getAnnotationClass() != null) {
                mapperScannerConfigurer.setAnnotationClass(scanner.getAnnotationClass());
            }
            if (scanner.getMarkerInterface() != null) {
                mapperScannerConfigurer.setMarkerInterface(scanner.getMarkerInterface());
            }
            return mapperScannerConfigurer;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy