万籁俱寂,万字将成。
刘耀文
Stay hungry. Stay foolish.
© 2024-2026
Powered by Mix Space&
余白 / Yohaku
.
正在被0人看爆
关于
关于本站关于我
更多
时间线友链
联系
写留言发邮件 ↗
刘耀文
Stay hungry. Stay foolish.
链接
关于本站·关于我·时间线·友链·写留言·发邮件
© 2024-2026 Powered by Mix Space&
余白 / Yohaku
.
正在被0人看爆
赣ICP备2024031666号
RSS 订阅·站点地图·
··|
RSS 订阅·站点地图·|··|赣ICP备2024031666号
稍候片刻,月出文自明。

Mybatis 自动配置原理

(已编辑)
/
50
AI·GEN

关键洞察

这篇文章上次修改于,可能部分内容已经不适用,如有疑问可询问作者。

阅读此文章之前,你可能需要首先阅读以下的文章才能更好的理解上下文。

  • Mybatis[plus] 源码阅读笔记

Mybatis 自动配置原理

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • 自动配置无非涉及几个方面

    • mapper的代理注册
    • sql语句的注册

    查看自动配置类

    路径 :D:\maven-repository\com\baomidou\mybatis-plus-boot-starter\3.4.1\mybatis-plus-boot-starter-3.4.1.jar!\META-INF\spring.factories

    代码解释

    JAVA
    @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})  
    @ConditionalOnSingleCandidate(DataSource.class)  
    @EnableConfigurationProperties(MybatisPlusProperties.class)  
    @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})  
    public class MybatisPlusAutoConfiguration implements InitializingBean {  
        // 省略其他字段...
        
        // 检查配置文件是否存在
        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)");  
            }  
        }  
    
        @Bean  
        @ConditionalOnMissingBean    
        public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {  
            // 创建 MybatisSqlSessionFactoryBean 实例,设置数据源
            MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();  
            factory.setDataSource(dataSource);  
            // 省略配置和设置其他属性...
            
            // 返回 SqlSessionFactory 实例
            return factory.getObject();  
        }  
    
        // 检查 Spring 容器中的 bean,并进行消费
        private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {  
            if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {  
                consumer.accept(this.applicationContext.getBean(clazz));  
            }  
        }  
    
        // 应用 Mybatis 配置
        private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {  
            MybatisConfiguration configuration = this.properties.getConfiguration();  
            // 省略其他配置...
            factory.setConfiguration(configuration);  
        }  
    
        @Bean  
        @ConditionalOnMissingBean    
        public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {  
            // 根据属性返回 SqlSessionTemplate 实例
            ExecutorType executorType = this.properties.getExecutorType();  
            return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);  
        }  
    
        // 省略其他内部类和方法...
    }
    
    

    这个类做了几件事

    • 注册SqlSessionFactory 这个类主要是为SqlSessionTemplate提供会话获取工厂 当然了 mybatisplus 进一步封装为了SqlSessionTemplate 为了和spring的事务结合
    • 注册SqlSessionTemplate
    • 注册AutoConfiguredMapperScannerRegistrar

    我们看看mapper是怎么注册到spring容器里面的

    JAVA
    public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {  
      
        private BeanFactory beanFactory;  
      
        @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");  
      
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);  
            if (logger.isDebugEnabled()) {  
                packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));  
            }  
      
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);  
            builder.addPropertyValue("processPropertyPlaceHolders", true);  
            builder.addPropertyValue("annotationClass", Mapper.class);  
            builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));  
            BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);  
            Stream.of(beanWrapper.getPropertyDescriptors())  
                // Need to mybatis-spring 2.0.2+  
                .filter(x -> x.getName().equals("lazyInitialization")).findAny()  
                .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));  
            registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());  
        }  
      
        @Override  
        public void setBeanFactory(BeanFactory beanFactory) {  
            this.beanFactory = beanFactory;  
        }  
    }
    
    

    可以看到这个类注册了一个BeanDefinition叫MapperScannerConfigurer,再进去看看

    CodeBlock Loading...

    可以看到 里面进一步使用ClassPathMapperScanner来扫描所有的 this.annotationClass类 而上面配置MapperScannerConfigurer的时候是扫描这个注解

    JAVA
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);  
    builder.addPropertyValue("processPropertyPlaceHolders", true);  
    builder.addPropertyValue("annotationClass", Mapper.class);   //这里
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
    

    其中ClassPathMapperScanner重写了doScan方法

    CodeBlock Loading...

    点进去processBeanDefinitions(beanDefinitions)看看

    CodeBlock Loading...

    可以看到将扫描的mapper包装为

    CodeBlock Loading...

    这个类最后会注册一个mapper代理

    CodeBlock Loading...

    我们进一步进去configuration.addMapper(this.mapperInterface); 看看怎么注册的

    CodeBlock Loading...

    这里进一步的将new MapperProxyFactory<>(type)注册到knownMappers中,我们点进去看看

    CodeBlock Loading...

    可以看到 最后是封装为一个MybatisMapperProxy对象

    CodeBlock Loading...

    好了 mapper怎么注册到spring的流程通了

    sql 语句什么时候注册进去的呢?

    我们注意到 注册mapper的时候下面还有两行语句

    CodeBlock Loading...

    这里就是注册sql语句的地方,我们点进去看看

    CodeBlock Loading...

    其中有一句loadXmlResource(); 我们点进去看看

    CodeBlock Loading...

    可以看到这里就是加载xml的地方String xmlResource = type.getName().replace('.', '/') + ".xml"; 这一句就是为什么xml要和mapper放在一个文件夹的关键了,直接是吧后缀替换为xml再去读取

    CodeBlock Loading...

    这里就是加载一些配置和一些sql语句了,接着就是注册mybatisplus自带的增删改查了

    CodeBlock Loading...
    JAVA
    public class MapperScannerConfigurer  
        implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {  
      
    @Override  
      public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {  
        if (this.processPropertyPlaceHolders) {  
          processPropertyPlaceHolders();  
        }  
      
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);  
        scanner.setAddToConfig(this.addToConfig);  
        scanner.setAnnotationClass(this.annotationClass);  
        scanner.setMarkerInterface(this.markerInterface);  
        scanner.setSqlSessionFactory(this.sqlSessionFactory);  
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);  
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);  
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);  
        scanner.setResourceLoader(this.applicationContext);  
        scanner.setBeanNameGenerator(this.nameGenerator);  
        scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);  
        if (StringUtils.hasText(lazyInitialization)) {  
          scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));  
        }  
        scanner.registerFilters();  
        scanner.scan(  
            StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));  
      }  
    
    
    JAVA
    @Override  
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {  
      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);  
      
      if (beanDefinitions.isEmpty()) {  
        LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)  
            + "' package. Please check your configuration.");  
      } else {  
        processBeanDefinitions(beanDefinitions);  
      }  
      
      return beanDefinitions;  
    }
    
    JAVA
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {  
      GenericBeanDefinition definition;  
      for (BeanDefinitionHolder holder : beanDefinitions) {  
        definition = (GenericBeanDefinition) holder.getBeanDefinition();  
        String beanClassName = definition.getBeanClassName();  
        LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName  
            + "' mapperInterface");  
      
        // the mapper interface is the original class of the bean  
        // but, the actual class of the bean is MapperFactoryBean    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59  
        definition.setBeanClass(this.mapperFactoryBeanClass);  
      
        definition.getPropertyValues().add("addToConfig", this.addToConfig);  
      
        boolean explicitFactoryUsed = false;  
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {  
          definition.getPropertyValues().add("sqlSessionFactory",  
              new RuntimeBeanReference(this.sqlSessionFactoryBeanName));  
          explicitFactoryUsed = true;  
        } else if (this.sqlSessionFactory != null) {  
          definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);  
          explicitFactoryUsed = true;  
        }  
      
        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {  
          if (explicitFactoryUsed) {  
            LOGGER.warn(  
                () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");  
          }  
          definition.getPropertyValues().add("sqlSessionTemplate",  
              new RuntimeBeanReference(this.sqlSessionTemplateBeanName));  
          explicitFactoryUsed = true;  
        } else if (this.sqlSessionTemplate != null) {  
          if (explicitFactoryUsed) {  
            LOGGER.warn(  
                () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");  
          }  
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);  
          explicitFactoryUsed = true;  
        }  
      
        if (!explicitFactoryUsed) {  
          LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");  
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);  
        }  
        definition.setLazyInit(lazyInitialization);  
      }  
    }
    
    JAVA
    definition.setBeanClass(this.mapperFactoryBeanClass);  
    
    JAVA
    @Override  
    protected void checkDaoConfig() {  
      super.checkDaoConfig();  
      
      notNull(this.mapperInterface, "Property 'mapperInterface' is required");  
      
      Configuration configuration = getSqlSession().getConfiguration();  
      if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {  
        try {  
          configuration.addMapper(this.mapperInterface);  
        } catch (Exception e) {  
          logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);  
          throw new IllegalArgumentException(e);  
        } finally {  
          ErrorContext.instance().reset();  
        }  
      }  
    }  
      
    /**  
     * {@inheritDoc}  
     */@Override  
    public T getObject() throws Exception {  
      return getSqlSession().getMapper(this.mapperInterface);  
    }
    
    JAVA
        public <T> void addMapper(Class<T> type) {  
            if (type.isInterface()) {  
                if (hasMapper(type)) {  
                    // TODO 如果之前注入 直接返回  
                    return;  
                    // TODO 这里就不抛异常了  
    //                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");  
                }  
                boolean loadCompleted = false;  
                try {              
                    knownMappers.put(type, new MybatisMapperProxyFactory<>(type));  
                    MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);  
                    parser.parse();  
                    loadCompleted = true;  
                } finally {  
                    if (!loadCompleted) {  
                        knownMappers.remove(type);  
                    }  
                }  
            }  
        }
    
    JAVA
    public class MybatisMapperProxyFactory<T> {  
          
        @Getter  
        private final Class<T> mapperInterface;  
        @Getter  
        private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();  
        public MybatisMapperProxyFactory(Class<T> mapperInterface) {  
            this.mapperInterface = mapperInterface;  
        }  
      
        @SuppressWarnings("unchecked")  
        protected T newInstance(MybatisMapperProxy<T> mapperProxy) {  
            return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);  
        }  
      
        public T newInstance(SqlSession sqlSession) {  
            final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);  
            return newInstance(mapperProxy);  
        }  
    }
    
    JAVA
    public class MybatisMapperProxyFactory<T> {  
          
        @Getter  
        private final Class<T> mapperInterface;  
        @Getter  
        private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();  
        public MybatisMapperProxyFactory(Class<T> mapperInterface) {  
            this.mapperInterface = mapperInterface;  
        }  
      
        @SuppressWarnings("unchecked")  
        protected T newInstance(MybatisMapperProxy<T> mapperProxy) {  
            return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);  
        }  
      
        public T newInstance(SqlSession sqlSession) {  
            final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);  
            return newInstance(mapperProxy);  
        }  
    }
    
    JAVA
    MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);  
    parser.parse();  
    
    JAVA
    public void parse() {  
        String resource = type.toString();  
        if (!configuration.isResourceLoaded(resource)) {  
            loadXmlResource();  
            configuration.addLoadedResource(resource);  
            String mapperName = type.getName();  
            assistant.setCurrentNamespace(mapperName);  
            parseCache();  
            parseCacheRef();  
            InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type);  
            for (Method method : type.getMethods()) {  
                if (!canHaveStatement(method)) {  
                    continue;  
                }  
                if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()  
                    && method.getAnnotation(ResultMap.class) == null) {  
                    parseResultMap(method);  
                }  
                try {  
                    parseStatement(method);  
                    InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);  
                    SqlParserHelper.initSqlParserInfoCache(mapperName, method);  
                } catch (IncompleteElementException e) {  
    
                }  
            }  
            // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql  
            try {  
                // https://github.com/baomidou/mybatis-plus/issues/3038  
                if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {  
                    parserInjector();  
                }  
            } catch (IncompleteElementException e) {  
                configuration.addIncompleteMethod(new InjectorResolver(this));  
            }  
        }  
        parsePendingMethods();  
    }
    
    JAVA
    private void loadXmlResource() {  
        // Spring may not know the real resource name so we check a flag  
        // to prevent loading again a resource twice    // this flag is set at XMLMapperBuilder#bindMapperForNamespace    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {  
            String xmlResource = type.getName().replace('.', '/') + ".xml";  
            // #1347  
            InputStream inputStream = type.getResourceAsStream("/" + xmlResource);  
            if (inputStream == null) {  
                // Search XML mapper that is not in the module but in the classpath.  
                try {  
                    inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);  
                } catch (IOException e2) {  
                    // ignore, resource is not required  
                }  
            }  
            if (inputStream != null) {  
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());  
                xmlParser.parse();  
            }  
        }  
    }
    
    JAVA
    public void parse() {  
      if (!configuration.isResourceLoaded(resource)) {  
        configurationElement(parser.evalNode("/mapper"));  
        configuration.addLoadedResource(resource);  
        bindMapperForNamespace();  
      }  
      
      parsePendingResultMaps();  
      parsePendingCacheRefs();  
      parsePendingStatements();  
    }
    
    JAVA
    // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql  
    try {  
        // https://github.com/baomidou/mybatis-plus/issues/3038  
        if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {  
            parserInjector();  
        }  
    } catch (IncompleteElementException e) {  
        configuration.addIncompleteMethod(new InjectorResolver(this));  
    }