zoukankan      html  css  js  c++  java
  • 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);
        }
  • 相关阅读:
    数学+高精度 ZOJ 2313 Chinese Girls' Amusement
    最短路(Bellman_Ford) POJ 1860 Currency Exchange
    贪心 Gym 100502E Opening Ceremony
    概率 Gym 100502D Dice Game
    判断 Gym 100502K Train Passengers
    BFS POJ 3278 Catch That Cow
    DFS POJ 2362 Square
    DFS ZOJ 1002/HDOJ 1045 Fire Net
    组合数学(全排列)+DFS CSU 1563 Lexicography
    stack UVA 442 Matrix Chain Multiplication
  • 原文地址:https://www.cnblogs.com/xieyanke/p/12143501.html
Copyright © 2011-2022 走看看