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。

posted on 2020-03-07 20:10  FFStayF  阅读(737)  评论(0编辑  收藏  举报