debug源码之mybatis

最近debug了一下mybatis,记录一下过程。

两个xml:

mybatis-config.xml:
<configuration>
    <properties resource="datasource.properties"></properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${db.driverClassName}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.username}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/Learn_resourceMapper.xml"/>
    </mappers>
</configuration>

Learn_resourceMapper.xml:
<mapper namespace="com.mall.dao.Learn_resourceMapper">
  <resultMap id="BaseResultMap" type="com.mall.pojo.Learn_resource">
    <constructor>
      <idArg column="id" javaType="java.lang.Long" jdbcType="BIGINT" />
      <arg column="author" javaType="java.lang.String" jdbcType="VARCHAR" />
      <arg column="title" javaType="java.lang.String" jdbcType="VARCHAR" />
      <arg column="url" javaType="java.lang.String" jdbcType="VARCHAR" />
    </constructor>
  </resultMap>
  <sql id="Base_Column_List">
    id, author, title, url
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from learn_resource
    where id = #{id,jdbcType=BIGINT}
  </select>
 </mapper>

dao:

public interface Learn_resourceMapper {
    Learn_resource selectByPrimaryKey(Long id);
}

test:

public class TestMain {
    @Test
    public void test1() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            Learn_resourceMapper mapper = sqlSession.getMapper(Learn_resourceMapper.class);
            Learn_resource learn_resource = mapper.selectByPrimaryKey(1056L);
            System.out.println(learn_resource.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}

开始debug,第三句进来看看 。

//org.apache.ibatis.session.SqlSessionFactoryBuilder
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

 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.
      }
    }
  }

看看①这句话干了什么:

//org.apache.ibatis.builder.xml.XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration()); //初始了一个Configuration
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false; 
    this.environment = environment;
    this.parser = parser;
  }
 public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver); 
    this.document = createDocument(new InputSource(inputStream)); 
  }

 private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

可以看出构造了一个XPathParser,并且将mybatis-config.xml的转换成的inputStream 赋值给了XPathParser的document属性。
然后XMLConfigBuilder持有了这个了XPathParser。


然后看看②这句话干了什么: build(parser.parse());

首先看parser.parse()

//org.apache.ibatis.builder.xml.XMLConfigBuilder
 public Configuration parse() {
    if (parsed) { 
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true; //表示解析过
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

parser.evalNode(“/configuration”) :这是解析了xml中的configuration下的内容。

接着看parseConfiguration():这是一个解析xml全过程。 解析的每一步内容最后都是加载到Configuration持有的属性、对象上。
Configuration可以说是mybatis最重要的类之一,负责组装框架的配置内容。

//类org.apache.ibatis.builder.xml.XMLConfigBuilder
private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      propertiesElement(root.evalNode("properties"));  //<properties resource="datasource.properties"/> 给configuration持有的Properties赋值
      loadCustomVfs(settings);
      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"));
      mapperElement(root.evalNode("mappers"));  //mappers.xml文件
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

具体来看看 mapperElement(root.evalNode(“mappers”));

//org.apache.ibatis.builder.xml.XMLConfigBuilder
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");
          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);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

这次代码走的是

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();
}

有没有觉得跟上面很类似,也是一个inputStream 、Builder。进这个构造方法看看。

//org.apache.ibatis.builder.xml.XMLMapperBuilder
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

就是一脉相承。目的就是解析Learn_resourceMapper.xml文件。看看怎么解析的好了,进入 mapperParser.parse();

//方法org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}

先看 configurationElement(parser.evalNode(“/mapper”));

//org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace); //com.mall.dao.Learn_resourceMapper
      cacheRefElement(context.evalNode("cache-ref"));  //null
      cacheElement(context.evalNode("cache")); //null
      parameterMapElement(context.evalNodes("/mapper/parameterMap")); //null
      resultMapElements(context.evalNodes("/mapper/resultMap")); //给configuration的resultMaps添加元素
      sqlElement(context.evalNodes("/mapper/sql")); //sql片段
      buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
}

buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)):
给configuration的mappedStatements添加元素。

ms.id = com.mall.dao.Learn_resourceMapper.selectByPrimaryKey

再看 bindMapperForNamespace();

//org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
  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);
          configuration.addMapper(boundType);
        }
      }
    }
  }

这个boundType就是dao层的接口。看看 configuration.addMapper(boundType);

//org.apache.ibatis.session.Configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}

再进去。

//org.apache.ibatis.binding.MapperRegistry
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 {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
}

添加了dao的一个mapper接口和它的代理类到Configuration的MapperRegistry的HashMap中。

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource(); //已加载过
      configuration.addLoadedResource(resource);//resource:三个
      //mappers/Learn_resourceMapper.xml
      //interface com.mall.dao.Learn_resourceMapper(mapper接口
      //namespace:com.mall.dao.Learn_resourceMapper(mapper.xml)
      assistant.setCurrentNamespace(type.getName());
      parseCache(); //null
      parseCacheRef(); //null
      Method[] methods = type.getMethods(); //接口里的方法
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);  //主要是加载接口文件中用注解方法的sql语句,本例中无
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

一起都加载的差不多了,让我们回到最初的起点。

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.
      }
    }
}

看看怎么加载configutation到sqlsession的

 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

来到test文件的

SqlSession sqlSession = sqlSessionFactory.openSession();

SqlSession是mybatis最重要的类之一,看看怎么开启回话的

//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

//Executor的SIMPLE类型
public ExecutorType getDefaultExecutorType() {
    return defaultExecutorType;
}

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
       //datasource
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //org.apache.ibatis.transaction.jdbc.JdbcTransaction
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //org.apache.ibatis.executor.SimpleExecutor
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

最后返回了接口SqlSession的实现类DefaultSqlSession;

 public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

至此,mybatis的三大最重要类都出现了:分别是SqlSession、Configuration、Executor。

回到test.java

try {
            Learn_resourceMapper mapper = sqlSession.getMapper(Learn_resourceMapper.class);
            Learn_resource learn_resource = mapper.selectByPrimaryKey(1056L);
            System.out.println(learn_resource.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }

先从Configuration里面的MapperRegistry里面得到Learn_resourceMapper的代理类。

//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#newInstance
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

//org.apache.ibatis.binding.MapperProxyFactory#newInstance
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

//org.apache.ibatis.binding.MapperProxy#MapperProxy
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession; //DefaultSqlSession
    this.mapperInterface = mapperInterface; //com.mall.dao.Learn_resourceMapper
    this.methodCache = methodCache; //null
  }

调用: Learn_resource learn_resource = mapper.selectByPrimaryKey(1056L);

实质上就是在调用org.apache.ibatis.binding.MapperProxy#invoke

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {  //false
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method); 
    return mapperMethod.execute(sqlSession, args);
  }

org.apache.ibatis.binding.MapperProxy#cachedMapperMethod
构造了MapperMethod ,并添加进了methodCache。
MapperMethod的command:
name: com.mall.dao.Learn_resourceMapper.selectByPrimaryKey;
type:select

 private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
}

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); //para参数为1056
          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;
  }

org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne

  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

查找一条实质就是查找list中的第一条

 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
     //查找到了id为com.mall.dao.Learn_resourceMapper.selectByPrimaryKey的ms.
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

query方法经过一系列的跳转到了org.apache.ibatis.executor.SimpleExecutor#doQuery

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

其中的:org.apache.ibatis.executor.SimpleExecutor#prepareStatement

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //打开了数据库连接 PooledConnection
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    //给sql语句赋值
    handler.parameterize(stmt);
    return stmt;
  }

org.apache.ibatis.executor.statement.PreparedStatementHandler#query

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
     //执行sql
    ps.execute();
    //给实体类赋值
    return resultSetHandler.<E> handleResultSets(ps);
  }

结束。

整个流程引用一张流程图:
这里写图片描述

评论 1 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

神一合一

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值