2021-05-28 21:55  阅读(1636)
文章分类:Mybatis 源码分析 文章标签:MybatisMybatis 源码
©  原文作者:ashan_li 原文地址:https://blog.csdn.net/ashan_li/category_6047775.html

整体加载过程

  1. mybatis-config.xml中可以在包(package)和class的方法让Mybatis加载一个Mapper

  2. 通过包的方式是从包加载所有的class,最条还是通过class方法来实现加载

  3. 加载过程中主要通过读取方法中的注解来生成MappedStatement对象,再加入到Configuration中。这个过程跟解析mapper.xml配置文件中的select/insert/update/delete节点的过程差不多。一个是从xml里读取信息,一个是从注解里读取信息。

  4. 通过注解生成的MappedStatement的Id生成规则为接口全限定名名加方法的名字,所以在定义Mapper接口类时千万不要重载方法,否则会发生预想不到的问题。

  5. Mapper接口中的只有包含如下八种注解中的一种才会被Mybatis解析成一个MappedStatement对象。

    1. Select/Insert/Update/Delete
    2. SelectProvider/InsertProvider/UpdateProvider/DeleteProvider

    一个方法如果没有这些注解中的其中一个,执行时Mybatis会报MappedStatement找不到

  6. Mybatis加载一个Mapper接口时会为其生成一个MapperProxyFactory对象,由这个对象来创建Mapper接口的实例。MapperProxyFactory这个类的实现在下一节详细分析。

Mapper的配置加载是从XmlConfigBuilder.mapperElement()方法中触发的

        private void mapperElement(XNode parent) throws Exception {
            if (parent != null) {
              for (XNode child : parent.getChildren()) {
    
                if ("package".equals(child.getName())) {
                  //通过package的方法
                  String mapperPackage = child.getStringAttribute("name");
                  configuration.addMappers(mapperPackage);
                } else {
                  String resource = child.getStringAttribute("resource");
                  String url = child.getStringAttribute("url");
                  String mapperClass = child.getStringAttribute("class");
                  if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                  } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                  } else if (resource == null && url == null && mapperClass != null) {
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    //指定class的方式
                    configuration.addMapper(mapperInterface);
                  } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                  }
                }
              }
            }
          }
    

通过package方式加载,最终也是找出所有符合条件的mapper类,再通过class的方法加载。

先来看configuration.addMaper()方法

         public <T> void addMapper(Class<T> type) {
            //最终是由MapperRegistry对象来完成加载的
            mapperRegistry.addMapper(type);
          }
    
         public <T> void addMapper(Class<T> type) {
            if (type.isInterface()) {
              if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
              }
              boolean loadCompleted = false;
              try {
                //这里为一个Mapper生成一个代理工厂,这个代理工厂通过JDK动态代理生成一个对象,稍后再详细分析。
                knownMappers.put(type, new MapperProxyFactory<T>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                //Mapper.xml是交由XMLMapperBuilder来解析的
                //同样这里使用了MapperAnnotationBuilder来解析注解
                parser.parse();
                loadCompleted = true;
              } finally {
                if (!loadCompleted) {
                  knownMappers.remove(type);
                }
              }
            }
    

MapperAnnotationBuilder.parse()方法

        public void parse() {
            String resource = type.toString();
            if (!configuration.isResourceLoaded(resource)) {
              //这里会先去加载相对应的mapper.xml配置文件
              //也就是说解析UserDao注解时,如果发现有UserDao.xml配置文件会先加载UesrDao.xml配置文件
              loadXmlResource();
              configuration.addLoadedResource(resource);
              assistant.setCurrentNamespace(type.getName());
              parseCache();
              parseCacheRef();
              //获取class中所有的方法
              Method[] methods = type.getMethods();
              for (Method method : methods) {
                try {
                  //解析方法
                  parseStatement(method);
                } catch (IncompleteElementException e) {
                  configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
              }
            }
            parsePendingMethods();
          }
    
          //这个方法的功能就是读取方法的注解,生成一个MappedStatement对象,然后加入到Configuration中
          void parseStatement(Method method) {
            Class<?> parameterTypeClass = getParameterType(method);
            LanguageDriver languageDriver = getLanguageDriver(method);
            //从注解中获取一个SqlSource,之前已经分析过MappedStatement对应一个SqlSource对象,表示配置的Sql
            SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
            //如果sqlSource为空,这个方法将直接返回
            if (sqlSource != null) {
              //读取方法中的Options注解
              Options options = method.getAnnotation(Options.class);
              //注意这个mappedStatementId的生成规则,Mybatis生成的Mapper代理对象也是根据这个规则来生成一个mappedStatementId,再去Configuration中加载MappedStatement的
              final String mappedStatementId = type.getName() + "." + method.getName();
              Integer fetchSize = null;
              Integer timeout = null;
              StatementType statementType = StatementType.PREPARED;
              ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
              SqlCommandType sqlCommandType = getSqlCommandType(method);
              boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
              //注意这个
              //这两个是设置二级缓存的,如果没有设置options注解,将使用如下默认值
              //如果是update/insert/delete语句,就行里就会刷新缓存,select语句则不刷新
              //如果是select语句是默认使用缓存的
              boolean flushCache = !isSelect;
              boolean useCache = isSelect;
    
              KeyGenerator keyGenerator;
              String keyProperty = "id";
              String keyColumn = null;
    
              //自动生成主键,一般在应用中比较少用
              if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
                // first check for SelectKey annotation - that overrides everything else
                SelectKey selectKey = method.getAnnotation(SelectKey.class);
                if (selectKey != null) {
                  keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                  keyProperty = selectKey.keyProperty();
                } else {
                  if (options == null) {
                    keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
                  } else {
                    keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
                    keyProperty = options.keyProperty();
                    keyColumn = options.keyColumn();
                  }
                }
              } else {
                keyGenerator = new NoKeyGenerator();
              }
    
              if (options != null) {
                flushCache = options.flushCache();
                useCache = options.useCache();
                fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
                timeout = options.timeout() > -1 ? options.timeout() : null;
                statementType = options.statementType();
                resultSetType = options.resultSetType();
              }
    
              //获取对应的resultMap,还可以配置多个,用","分隔
              String resultMapId = null;
              ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
              if (resultMapAnnotation != null) {
                String[] resultMaps = resultMapAnnotation.value();
                StringBuilder sb = new StringBuilder();
                for (String resultMap : resultMaps) {
                  if (sb.length() > 0) sb.append(",");
                  sb.append(resultMap);
                }
                resultMapId = sb.toString();
              } else if (isSelect) {
                //如果没有配置resultMap,自动生成一个加入到Configuration中,就像Mapper.xml配置文件中只配置了resultType属性那样
                resultMapId = parseResultMap(method);
              }
    
              //这里跟加载mapper.xml配置文件中一样,交由助手去生成一个MappedStatement并加入到Configuration中
              assistant.addMappedStatement(
                  mappedStatementId,
                  sqlSource,
                  statementType,
                  sqlCommandType,
                  fetchSize,
                  timeout,
                  null,                             // ParameterMapID
                  parameterTypeClass,
                  resultMapId,    // ResultMapID
                  getReturnType(method),
                  resultSetType,
                  flushCache,
                  useCache,
                  false, // TODO issue #577
                  keyGenerator,
                  keyProperty,
                  keyColumn,
                  null,
                  languageDriver,
                  null);
            }
          }
    

再来看是怎么生成SqlSource对象的

         private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
            try {
              //获取方法的注解,Select/Update/Insert/Delete中的一种
              Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
              //获取方法的注解,SelectProvider/UpdateProvider/InsertProvider/DeleteProvider中的一种
              Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
    
              if (sqlAnnotationType != null) {
                if (sqlProviderAnnotationType != null) {
                  //不支持两种注解同时存在
                  throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
                }
    
                Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
                //直接读取Select/Update/Insert/Delete中的sql内容
                final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
                //通过sql创建一个SqlSource,这里的逻辑眼从mapper.xml文件读取里的逻辑差不多,不再讨论
                return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
              } else if (sqlProviderAnnotationType != null) {
                Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
                //这个SqlSource主要是通过反射执行SelectProvider/UpdateProvider/InsertProvider/DeleteProvider的方法得到sql内容,也不再讨论
                return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
              }
              return null;
            } catch (Exception e) {
              throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
            }
          }
    
点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Mybatis3源码分析(20)-Mapper实现-配置加载
上一篇
Mybatis3源码分析(19)-Mapper生成过程-示例
下一篇
Mybatis3源码分析(21)-Mapper实现-动态代理