• mybatis——sql.xml解析及接口映射器


    mybatis-conf.xml解析:主要弄清楚sql.xml的解析成什么了,为后面直接执行+接口映射器做准备。

    一、配置文件

    mybatis-conf.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!-- 配置文件的根元素 -->
    <configuration>
        <!-- 属性:定义配置外在化 -->
        <properties resource="db.properties" />
    
        <!-- 环境:配置mybatis的环境 -->
        <environments default="dev">
            <!-- 环境变量:可以配置多个环境变量,比如使用多数据源时,就需要配置多个环境变量 -->
            <environment id="dev">
                <!-- 事务管理器 -->
                <transactionManager type="JDBC"></transactionManager>
                <!-- 数据源 -->
                <dataSource type="POOLED">
                    <property name="driver" value="${mysql.driver}"/>
                    <property name="url" value="${mysql.url}"/>
                    <property name="username" value="${mysql.username}"/>
                    <property name="password" value="${mysql.password}"/>
                </dataSource>
            </environment>
        </environments>
        <!-- 映射器:指定映射文件或者映射类 -->
        <mappers>
            <mapper resource="mapper/UserMapper.xml"/>
        </mappers>
    </configuration>

    userMapper.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.app.aop.transactional.mapper.UserMapper">
      <resultMap id="BaseResultMap" type="com.app.aop.transactional.model.User">
        <id column="id" jdbcType="BIGINT" property="id" />
        <result column="user_id" jdbcType="BIGINT" property="userId" />
        <result column="user_name" jdbcType="VARCHAR" property="userName" />
      </resultMap>
      <sql id="Base_Column_List">
        id, user_id, user_name
      </sql>
      <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select 
        <include refid="Base_Column_List" />
        from user
        where id = #{id,jdbcType=BIGINT}
      </select>
    </mapper>

    二、xml解析生成Configureration对象

    xml配置解析主要是下面两行代码:

    // 读取mybatis-config.xml文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-conf.xml");
    // 初始化mybatis,创建SqlSessionFactory类的实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    new SqlSessionFactoryBuilder().build(inputStream):解析mybatis-conf.xml并生成sqlSessionFactory对象
    /* org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties) */
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }

    Configration对象是通过XMLConfigBuilder.parse()解析的

    /* org.apache.ibatis.builder.xml.XMLConfigBuilder#parse */
    
      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());//生成一个new Configuration()对象
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }
    
      public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    
      private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          configuration.typeAliasRegistry<string,Class>
          typeAliasesElement(root.evalNode("typeAliases"));  
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          //重点看看sql.xml的解析成了什么
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }

    mapperElement(root.evalNode("mappers)):解析sql.xml到Configuration对象中

      private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              //将package下多个sql.xml解析到Configuration对象中,
              //便于理解,研究下面的单个sql.xml解析
              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);
                //sql.xml解析到Configuration对象中
                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);
                //sql.xml解析到Configuration对象中
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url == null && mapperClass != null) {
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                //接口直接绑定,直接接口方法注解@select等解析成sql
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }

    ① sql.xml解析

    /* org.apache.ibatis.builder.xml.XMLMapperBuilder#parse */
      public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          //解析sql.xml(根节点<mapper>)
          //将<insert><delete><update><select>解析成一个MappedStatement对象
          //然后放入到configuration对象的mappedStatements容器中(Map类型)
          //<namespace+id , mappedStatement>
          configurationElement(parser.evalNode("/mapper"));
          //打标
          configuration.addLoadedResource(resource);
          //加入到configuration.mapperRegistry.knownMappers容器中(Map类型)
          //type = class.forName(namespace)
          //Map<type,new MapperProxyFactory<T>(type)>
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }

    解析生成的mappedStatement对象的属性。

      public void parseStatementNode() {
        //<sql>标签中的id属性
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
        //判断对应的databaseId中是否已存在id=namespace+id
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
    
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        //<sql>中的parameterMap属性 参数Map
        String parameterMap = context.getStringAttribute("parameterMap");
        //<sql>中的paramterType属性 参数类型
        String parameterType = context.getStringAttribute("parameterType");
        //解析parameterType 
        //① 类型别名typeAlias直接TYPE_ALIASES.get(paramterType)
        //② 不是别名Class.forName(paramterType)
        Class<?> parameterTypeClass = resolveClass(parameterType);
        //<sql>中resultMap属性  返回参数Map
        String resultMap = context.getStringAttribute("resultMap");
        //<sql>中resultType属性  返回参数类型
        String resultType = context.getStringAttribute("resultType");
        //<sql>中lang属性  不常用,指定Diver类型常用的mysql
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
        //返回类型 别名转class,不是别名直接Class.forName(type)
        Class<?> resultTypeClass = resolveClass(resultType);
        //<sql>的resultSetType  不常用
        String resultSetType = context.getStringAttribute("resultSetType");
        //<sql>的statementType属性,编译类型,默认PreparedStatement
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        //是否<select>标签
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        //<sql>的flushCache属性,是否清除缓存 默认<select>:false ; 非<select>:true
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        //<sql>的userCache属性,是否使用缓存  默认<select>:true;非<select> : false
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        //<sql>的resultOrdered属性 不常用
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes and remove them.
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        //sql语句解析生成sql,三部分 sql语句+parameterMapping+parameterObject
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
          keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
          keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
              configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
              ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
      //创建mappedStatement对象,并初始化变量
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }

    ② 注解解析:

    sql.xml解析时bindMapperForNamespace的主题逻辑也是执行addMapper(type)。

    /* org.apache.ibatis.binding.MapperRegistry#addMapper */
      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 {
            //加入到configuration.mapperRegistry.knownMappers容器中(Map类型)
            //type = class.forName(namespace)
            //Map<type,new MapperProxyFactory<T>(type)>
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            // 将type中methods上的注解@Insert@Delete@Update@Select转化为一个MapperStatement对象放入到configuration.mappedStatements容器中
            //<type.getName+method.getName , mapperStatement>
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }

    小结一下sql的解析:支持sql.xml解析,支持接口解析,下面type.getName == namespace、method.getName == id

    ① 解析sql为一个MapperStatement对象,然后以<namespace+id , mapperStatement>的映射关系存储到configuration的容器mappedStatements中

    ② new了一个MapperProxyFactory对象,然后以<type , mapperProxyFactory>的映射关系存储到容器configuration.mapperRegistry.knownMappers中

    三、接口映射器

    看过sql的解析,然后来研究下sql执行时映射关系。

            // 操作数据库方法一(ibatis主要使用方式):获得xml映射文件中定义的操作语句
            User s = session.selectOne("com.app.aop.transactional.mapper.UserMapper.selectByPrimaryKey", 1L);
            // 打印Student对象
            System.out.println(s);
    
            // 操作数据库方法二(mybatis主要使用方式):获得mapper接口的代理对象
            UserMapper sm = session.getMapper(UserMapper.class);
            // 直接调用接口的方法,查询id为1的Student数据
            User s2 = sm.selectByPrimaryKey(1L);
            // 打印Peson对象
            System.out.println(s2);

    方法一:很简单就是从configuration.mappedStatements.get(namespace+id),然后执行.主要看方法二:接口映射器实现

    1、接口映射器实现

    sessiong.getMapper()-->configuration.getMapper()-->configuration.mapperRegistry.getMapper()

    /* org.apache.ibatis.binding.MapperRegistry#getMapper */
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
    /* org.apache.ibatis.binding.MapperProxyFactory*/
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
      protected T newInstance(MapperProxy<T> mapperProxy) {
        //实际用JDK动态代理生成了一个代理对象proxy object
        //mapperProxy是InvokeHandler实例
        //那么target object是什么
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }

     MapperProxy.invoke() : 动态代理方法执行,好像没有target object 主要是proxy object 中每个method对象都代理sqlsession的一个行为。

    例如:userMapper.selectUserById(1L)实际proxyObject.selectUserById(1L)--->sqlSession.selete("com.app.aop.transactional.mapper.UserMapper.selectByPrimaryKey",1L);

    /* org.apache.ibatis.binding.MapperProxy#invoke */
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            //Object的方法直接执行mapperProxy的方法
            return method.invoke(this, args);
          } else if (isDefaultMethod(method)) {
            //静态方法直接执行
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        //new MapperMethod(mapperInterface, method, configuration)
        //实际new sqlCommond(mapperInterface, mehod, configuration)
        //sqlCommond.name = mapperStatement.getId
        //sqlCommond.type = ms.getSqlCommondType()--insert|delete|update|select
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //sqlSession根据sqlCommondType执行对应sql,例如当sqlCommondType == select时
        //sqlsession.select(sqlCommond.name)
        return mapperMethod.execute(sqlSession, args);
      }

    mapperMethod.execute():sql执行

    /* org.apache.ibatis.binding.MapperMethod#execute */
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
          Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
          }
          case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
            }
            break;
          case FLUSH:
            result = sqlSession.flushStatements();
            break;
          default:
            throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName() 
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
      }

    四、总结

    1、<Configuration>标签实际解析成了一个Configuration对象

    2、mapper.xml实际是<configuration>里的一个<mapper>标签,mapper.xml中多个的<select|delete|update|insert>解析成对应的多个MappedStatement对象放入到configuration.mappedStatements(Map<namespace+id, mappedStatement)中

    3、接口映射器的实质是通过JDK动态代理,生成一个mapperInterface接口的代理对象,InvokeHandler实例是new MapperProxy(mapperInterface),调用接口方法实际是调用代理对象的方法,在MapperProxy.invoke方法中调用具体的sqlSession.selectOne(namespace+id, parameters),找到上面mappedStatement然后执行sql。

  • 相关阅读:
    第9章 使用ssh服务管理远程主机。
    Linux下的网络管理工具—OpenNMS
    第8章 Iptables与Firewalld防火墙
    Linux下更好用的帮助命令—cheat
    第7章 使用RAID与LVM磁盘阵列技术
    收藏的博客
    linux下vi编辑器常用命令
    搜索引擎高级使用技巧
    七牛云配置二级域名
    软考-系统架构师备考知识(一)
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12436771.html
Copyright © 2020-2023  润新知