• Mybatis苞米豆源码分析一: 动态注入


    启动过程分析: 与绝大部分starter一样, 使用spring.factories作为入口

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusAutoConfiguration

    简要说明动态SQL注入的流程:

    1. 先对XML进行解析, 基于原生的mybatis解析XML方式, 解析成statement并存入configuration中
    2. 根据第一步的解析可以获取当前XML的namespace,也即 mapper类判断当前Mapper接口是否继承 BaseMapper(只有继承了BaseMapper方法才需要动态注入SQL),
      然后动态注入BaseMapper中方法(有多个注入器, 文章最末尾代码段)
    3. 最后再对所有Mapper方法进行筛选, 判断方法是否使用注解动态注入SQL方式, 若使用了注解则覆盖前两步骤生成的statement(SelectProvider, InsertProvider, UpdateProvider)

    配置构造

    public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,
                                            ObjectProvider<Interceptor[]> interceptorsProvider, //spring注入 可以理解为@AutoWare
                                            ResourceLoader resourceLoader,
                                            ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                            ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ApplicationContext applicationContext) {
            this.properties = properties;
            this.interceptors = interceptorsProvider.getIfAvailable();
            this.resourceLoader = resourceLoader;
            this.databaseIdProvider = databaseIdProvider.getIfAvailable();
            this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
            this.applicationContext = applicationContext;
        }

    初始化核心类SqlSessionFactory MybatisPlusAutoConfiguration

     //注入 SqlSessionFactory
        @Bean
        @ConditionalOnMissingBean
        public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
            //SqlSessionFactory生成
            MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
            //...忽略若干行MybatisSqlSessionFactoryBean的属性设置
            
            
            //注入填充器 针对比较通用的字段 举例:插入数据是自动填充 valid gmt_create gmt_modify 修改数据时自动填充gmt_modify
            if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class, false,
                false).length > 0) {
                MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);
                globalConfig.setMetaObjectHandler(metaObjectHandler);
            }
            //注入主键生成器  做insert操作的时候 自动填充ID 不过由于大部分情况下主键需要用来承上启下, 不建议使用
            if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,
                false).length > 0) {
                IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);
                globalConfig.setKeyGenerator(keyGenerator);
            }
            //注入sql注入器  这个比较重要 可以在这里注入自定义的SQL注入器, 苞米豆自带一个逻辑处理器LogicSqlInjector,注入到Spring容器后,能在此处拿到
              (执行delete方法的时候 变成update逻辑字段)
            if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,
                false).length > 0) {
                //从容器中取
                ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);
                globalConfig.setSqlInjector(iSqlInjector);
            }
            
            //重点关注的是 SqlSessionFactory对象创建过程
            return factory.getObject();
        }

    创建SqlSessionFactory对象 MybatisSqlSessionFactoryBean

    @Override
        public SqlSessionFactory getObject() throws Exception {
            if (this.sqlSessionFactory == null) {
                //重点关注方法 初始化操作
                afterPropertiesSet();
            }
            return this.sqlSessionFactory;
        }
        
        @Override
        public void afterPropertiesSet() throws Exception {
            //前置条件判断
            notNull(dataSource, "Property 'dataSource' is required");
            notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
            state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
                "Property 'configuration' and 'configLocation' can not specified with together");
                
            //重点关注 构建sqlSessionFactory
            this.sqlSessionFactory = buildSqlSessionFactory();
        }
        
        protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    
            Configuration configuration;
    
            //加载自定义 MybatisXmlConfigBuilder  较少使用 忽略
            MybatisXMLConfigBuilder xmlConfigBuilder = null;
            .......
    
            // 自定义枚举类扫描处理 类型转换器注册(jdbc和java转换) 较少使用 非关心重点 忽略
            ........
    
            // 自定义类别名
            if (!isEmpty(this.typeAliases)) {
                for (Class<?> typeAlias : this.typeAliases) {
                    configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Registered type alias: '" + typeAlias + "'");
                    }
                }
            }
    
            // 重点关心 mybatis 拦截器注册, 几乎绝大部分mybatis插件都是使用拦截器方式实现, 将拦截器注册到configuration中
            if (!isEmpty(this.plugins)) {
                for (Interceptor plugin : this.plugins) {
                    configuration.addInterceptor(plugin);
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Registered plugin: '" + plugin + "'");
                    }
                }
            }
    
           //忽略其他自定义实现
           .....
    
            //设置spring事务管理工厂 其作用为新建Spring事务org.mybatis.spring.transaction.SpringManagedTransaction  非重点不关注
            if (this.transactionFactory == null) {
                this.transactionFactory = new SpringManagedTransactionFactory();
            }
            configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
            
            // 设置元数据相关 这里并非通过反射对Object对属性赋值 只是简单的将dataSource属性值赋给globalConfig 不要被名字误解
            GlobalConfigUtils.setMetaData(dataSource, globalConfig);
            SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration);
          
            // 各种sqlSessionFactory的属性赋值 忽略
            .......
          
            if (!isEmpty(this.mapperLocations)) {
                if (globalConfig.isRefresh()) {
                    //TODO 设置自动刷新配置 减少配置
                    new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
                        2, true);
                }
                for (Resource mapperLocation : this.mapperLocations) {
                    if (mapperLocation == null) {
                        continue;
                    }
    
                    try {
                        // TODO  这里也换了噢噢噢噢  这句话是官方原话
                        // mapperLocation可以理解为一个mybatis的Xml文件 作用为创建xml解析器 XMLMapperBuilder
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            configuration, mapperLocation.toString(), configuration.getSqlFragments());
                            
                        //对xml进行解析 重点关注
                        xmlMapperBuilder.parse();
                    } catch (Exception e) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                    } finally {
                        ErrorContext.instance().reset();
                    }
    
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
                    }
                }
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
                }
            }
            //返回此sqlSessionFactory
            return sqlSessionFactory;
        }

    对mybatis xml进行解析

    XMLMapperBuilder

    //总体逻辑分为两步 
      // 1 静态加载, 加载xml文件 注册xml文件中sql为 statement到 configration中
      // 2 动态加载, 判断方法是否在上一步已经注册为statement 若未注册则使用动态注册类进行 SQL动态注册statement到 configration中, 这取决于BaseMapper的基础方法数
      // 注: 由于第二步动态加载只对方法名进性判断 未对注解@Param中的参数进性容错处理 若进性自定义SQL覆盖BaseMapper中的方法,可能会导致报错
      public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          
          //重点关注 注册自定义xml的SQL方法
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          //重点关注 动态注册xml的sql方法  
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
      }
      
      //解析xml
      private void bindMapperForNamespace() {
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
          Class<?> boundType = null;
          try {
            boundType = Resources.classForName(namespace);
          } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
          }
          if (boundType != null) {
            if (!configuration.hasMapper(boundType)) {
              // Spring may not know the real resource name so we set a flag
              // to prevent loading again this resource from the mapper interface
              // look at MapperAnnotationBuilder#loadXmlResource
              configuration.addLoadedResource("namespace:" + namespace);
              //重点关注 注册mapper 此处 configuration.addMapper 被苞米豆重新实现 使用的是苞米豆的添加方法 继续往下看苞米豆的具体实现
              configuration.addMapper(boundType);
            }
          }
        }
      }

    注册 mapper

    MybatisPlusAutoConfiguration

    @Override
        public <T> void addMapper(Class<T> type) {
            //此注册器为苞米豆的mapper注册器 原生的为MapperRegistry不需要特意关注 重点关注苞米豆注册器MybatisMapperRegistry
            mybatisMapperRegistry.addMapper(type);
        }

    真正的注册类 储存mapper

    MybatisMapperRegistry

    @Override
        public <T> void addMapper(Class<T> type) {
            //若mapper类是接口则往下进行 若非接口也不报错 这点无法理解的
            if (type.isInterface()) {
                //判断是否已经注册了
                if (hasMapper(type)) {
                    // TODO 如果之前注入 直接返回
                    return;
                    // throw new BindingException("Type " + type +
                    // " is already known to the MybatisPlusMapperRegistry.");
                }
                boolean loadCompleted = false;
                try {
                    //此处是为了防止后面同样mybatis xml使用用一个mapper作为namespace 可以不用重复创建 见hasMapper(type)方法
                    knownMappers.put(type, new MapperProxyFactory<>(type));
                    
                    //终于到了终点代码 xml的解析实际是交给  MybatisMapperAnnotationBuilder来做的
                    MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                    parser.parse();
                    loadCompleted = true;
                } finally {
                    if (!loadCompleted) {
                        knownMappers.remove(type);
                    }
                }
            }
        }

    苞米豆的动态statement从这里开始

    MybatisMapperAnnotationBuilder

    @Override
        public void parse() {
            //获取mapper全路径
            String resource = type.toString();
            if (!configuration.isResourceLoaded(resource)) {
                //解析xml
                loadXmlResource();
                //设置当前mapper已经加载
                configuration.addLoadedResource(resource);
                assistant.setCurrentNamespace(type.getName());
                //缓存配置 忽略
                parseCache();
                parseCacheRef();
                //获取mapper所有方法 重点
                Method[] methods = type.getMethods();
                
                // TODO 注入 CURD 动态 SQL (应该在注解之前注入)  注入器见
                // 判断BaseMapper是否是当前mapper的接口或者父类 一个native方法
                if (BaseMapper.class.isAssignableFrom(type)) {
                    
                    //利用SQL注入器 根据方法名动态住处sql 相当于在xml写了一段sql, 然后解析成statemanet
                    //最终实现在AutoSqlInjector的injectSql方法 直接看下一段代码
                    GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
                }
                for (Method method : methods) {
                    try {
                        // 判断此方法是否为桥接方法 只处理非桥接方法 简单解释: 父类申明泛型但是不指定 而实现类指定具体的泛型 编译时确定了具体泛型
                        if (!method.isBridge()) {
                        
                            //最后进行注解覆盖 举例org.apache.ibatis.annotations.SelectProvider
                            parseStatement(method);
                        }
                    } catch (IncompleteElementException e) {
                        configuration.addIncompleteMethod(new MethodResolver(this, method));
                    }
                }
            }
            parsePendingMethods();
        }
      

    动态SQL注入器处理方法 AutoSqlInjector

    // 以下每个自动注入器注入动态SQL之前会判断是否已人为实现在mybatis xml中 若不存在才使用动态注入器
        // 举例: 在分库分表时 若使用自动注入器则会连org_id一并修改, 此时需要人为实现updateById, 苞米豆检测要人为实现则不会进行动态注入
        protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
            /**
             * #148 表信息包含主键,注入主键相关方法
             */
            if (StringUtils.isNotEmpty(table.getKeyProperty())) {
                /** 删除 */
                this.injectDeleteByIdSql(false, mapperClass, modelClass, table);
                this.injectDeleteByIdSql(true, mapperClass, modelClass, table);
                /** 修改 */
                this.injectUpdateByIdSql(true, mapperClass, modelClass, table);
                this.injectUpdateByIdSql(false, mapperClass, modelClass, table);
                /** 查询 */
                this.injectSelectByIdSql(false, mapperClass, modelClass, table);
                this.injectSelectByIdSql(true, mapperClass, modelClass, table);
            } else {
                // 表不包含主键时 给予警告
                logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
                    modelClass.toString()));
            }
            /**
             * 正常注入无需主键方法
             */
            /** 插入 */
            this.injectInsertOneSql(true, mapperClass, modelClass, table);
            this.injectInsertOneSql(false, mapperClass, modelClass, table);
            /** 删除 */
            this.injectDeleteSql(mapperClass, modelClass, table);
            this.injectDeleteByMapSql(mapperClass, table);
            /** 修改 */
            this.injectUpdateSql(mapperClass, modelClass, table);
            /** 修改 (自定义 set 属性) */
            this.injectUpdateForSetSql(mapperClass, modelClass, table);
            /** 查询 */
            this.injectSelectByMapSql(mapperClass, modelClass, table);
            this.injectSelectOneSql(mapperClass, modelClass, table);
            this.injectSelectCountSql(mapperClass, modelClass, table);
            this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
            this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
            this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
            this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
            this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
            /** 自定义方法 */
            this.inject(configuration, builderAssistant, mapperClass, modelClass, table);
        }
  • 相关阅读:
    python3给socket模块设置代理
    yield、greenlet与协程gevent
    线程池
    并发通信、生产者与消费者模型
    多进程和多线程
    非阻塞套接字与IO多路复用
    14.python模块之subprocess
    判断页面是否滑到底部
    @vue/cli 3.x 版本配置productionGzip提高性能
    vue跳转到指定位置
  • 原文地址:https://www.cnblogs.com/xieyanke/p/12143501.html
Copyright © 2020-2023  润新知