mybatis源码DEBUG学习

来源:互联网 发布:淘宝主播通过什么赚钱 编辑:程序博客网 时间:2024/06/01 07:20

不知道大家是如何去学习源码的,我学习源码都是先写一个小的demo,去debug,去看看这个开源项目的一些有趣的功能都是如何实现的。
以下就是我学习mybatis的相关源码的过程。在去学习去debugmabatis源码的时候有必要先写一个最简单的mybatis的demo。下面是主要相关xml配置文件和代码。
UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper        PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"        "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"><mapper namespace="mybatisTest.dao.UserDao">    <select id="findUserById" resultType="mybatisTest.model.User" >        select * from user where id = #{id}    </select></mapper>

mybatis.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配置文件, 我这里面配置的是数据库相关 -->    <properties resource="mybatisTest/dbConfig.properties"></properties>    <!-- 指定Mybatis使用log4j -->    <settings>        <setting name="logImpl" value="LOG4J"/>    </settings>    <environments default="development">        <environment id="development">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">                <!--                如果上面没有指定数据库配置的properties文件,那么此处可以这样直接配置              <property name="driver" value="com.mysql.jdbc.Driver"/>              <property name="url" value="jdbc:mysql://localhost:3306/test1"/>              <property name="username" value="root"/>              <property name="password" value="root"/>               -->                <!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->                <property name="driver" value="${driver}"/>                <property name="url" value="${url}"/>                <property name="username" value="${username}"/>                <property name="password" value="${password}"/>            </dataSource>        </environment>    </environments>    <!-- 映射文件,mybatis精髓, 后面才会细讲 -->    <mappers>        <mapper resource="mybatisTest/mapper/UserMapper.xml"/>    </mappers></configuration>

测试类

package mybatisTest.test;import mybatisTest.dao.UserDao;import mybatisTest.model.User;import com.sun.tools.javac.util.Assert;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Test;import java.io.IOException;import static com.sun.tools.doclint.Entity.or;/** * Created by sandsa on 17/1/6. */public class UserDaoTest {    @Test    public void findUserById() {        SqlSession sqlSession = getSessionFactory().openSession();        UserDao userMapper = sqlSession.getMapper(UserDao.class);        User user = userMapper.findUserById(2);        Assert.checkNull(user , "No data !");    }    //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与数据库进行交互    private static SqlSessionFactory getSessionFactory() {        SqlSessionFactory sessionFactory = null;        String resource = "mybatisTest/configuration.xml";        try {            sessionFactory = new SqlSessionFactoryBuilder().build(Resources                    .getResourceAsReader(resource));        } catch (IOException e) {            e.printStackTrace();        }        return sessionFactory;    }}

下面我们可以首先看一下这个sqlSession是如何生成的。在我们一步步的debug的时候,会发现主要和xPathparse、XMLconfigBuilder这两个类有关。

准备阶段

首先我们可以看出来,这个sqlSession的生成和sessionFactory的生成有关系,那我们先看这个sessionFactory是如何构造出来的。在代码中

sessionFactory = new SqlSessionFactoryBuilder().build(Resources                    .getResourceAsReader(resource));

我们先debug进去看Resources.getResourceAsReader(resource)是干什么的,正如我们所料这一句话的意思是把xml文件加载到内存中来,然后再调用essionFactory = new SqlSessionFactoryBuilder().build(reader)去构造这个对象,当我们debug进去build()这个方法的时候,我们会发现这个类SqlSessionFactoryBuilder有很多构造方法,基本上任何形式的读入xml文件都可以构造出一个sessionFactory对象。然后我们继续往下debug,会发现所有的build()方法最后都会调用他们的一个重载方法,

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

我们可以看到在这个方法中构造了一个XMLConfigBuilder对象,这就是我们前面说的解析这个xml很重要的一个类,那我们在继续debug这个构造函数进去,我们会发现这个类不仅构造了一个自己的对象还构造了一个XPathParser对象,这样上面我们所说的这两个很重要的类就都已经构造完成对象了,可以完成解析了。如果你足够好奇,还可以看下XPathParser对象是如何生成的,

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {    commonConstructor(validation, variables, entityResolver);    this.document = createDocument(new InputSource(reader));  }

你会看到在生成XPathParser对象的时候,赋值了一些变量和生成了一个dom对象,这个dom对象就是用来后续解析xml文件的。

解析xml文件阶段

当看到这里的时候你会基本上前面的准备工作就准备完成了,后续解析阶段其实不难。回到上面完成这两个对象构造的时候,就开始了解析xml文件的工作了。

XMLConfigBuilder中的parse()方法在解析这个xml文件,其中具体的获取各个节点的内容的方法是XPathPaser类中的evalNode()方法。

public Configuration parse() {    if (parsed) {      throw new BuilderException("Each XMLConfigBuilder can only be used once.");    }    parsed = true;    ==//获取整个configuration标签中的内容==    parseConfiguration(parser.evalNode("/configuration"));    return configuration;  }  private void parseConfiguration(XNode root) {    try {      //以下各个方法便是依次去解析各个标签中的内容,如果感兴趣可以点进去去详细的了解      //是如何去解析各个节点的      propertiesElement(root.evalNode("properties"));      Properties settings = settingsAsProperties(root.evalNode("settings"));      loadCustomVfs(settings);      typeAliasesElement(root.evalNode("typeAliases"));      pluginElement(root.evalNode("plugins"));      objectFactoryElement(root.evalNode("objectFactory"));      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));      reflectorFactoryElement(root.evalNode("reflectorFactory"));      settingsElement(settings);      environmentsElement(root.evalNode("environments"));      databaseIdProviderElement(root.evalNode("databaseIdProvider"));      typeHandlerElement(root.evalNode("typeHandlers"));      mapperElement(root.evalNode("mappers"));    } catch (Exception e) {      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);    }  }

这里我们可以看一下另一个很重要的部分--解析mapper配置文件。
在上面解析mybatis.xml的时候,有一行代码

mapperElement(root.evalNode("mappers"));

这一行是加载mybatis.xml中的mappers标签内的内容,可以看到在这个配置文件中我们有一个

<mapper resource="mybatisTest/mapper/UserMapper.xml"/>

下面mybatis就会去加载这个配置文件并依次去读取这个文件。
首先看这个mapperElement()方法

private void mapperElement(XNode parent) throws Exception {    if (parent != null) {      //遍历这个mappers中的每一个mapper配置文件      for (XNode child : parent.getChildren()) {        if ("package".equals(child.getName())) {          String mapperPackage = child.getStringAttribute("name");          configuration.addMappers(mapperPackage);        } else {          //获取这个mapper的文件的相关信息          String resource = child.getStringAttribute("resource");          String url = child.getStringAttribute("url");          String mapperClass = child.getStringAttribute("class");          //开始解析这个mapper文件          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.");          }        }      }    }  }

可以看到在遍历每个mapper文件的时候,首先会去先把这个配置文件加载到内存中,然后在去解析这个xml文件。
具体解析这个配置文件可以看到这个parse()方法。

public void parse() {    if (!configuration.isResourceLoaded(resource)) {      //解析这个mapper配置文件      configurationElement(parser.evalNode("/mapper"));      configuration.addLoadedResource(resource);      bindMapperForNamespace();    }    parsePendingResultMaps();    parsePendingChacheRefs();    parsePendingStatements();  }

最后我们可以看到解析这个mapper配置文件和解析mybatis.xml的套路简直如出一辙。可以看到configurationElement()这个方法。

private void configurationElement(XNode context) {  try {    //异常解析这个mapper文件的各个节点    String namespace = context.getStringAttribute("namespace");    if (namespace == null || namespace.equals("")) {      throw new BuilderException("Mapper's namespace cannot be empty");    }    builderAssistant.setCurrentNamespace(namespace);    cacheRefElement(context.evalNode("cache-ref"));    cacheElement(context.evalNode("cache"));    parameterMapElement(context.evalNodes("/mapper/parameterMap"));    resultMapElements(context.evalNodes("/mapper/resultMap"));    sqlElement(context.evalNodes("/mapper/sql"));    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));  } catch (Exception e) {    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);  }}

当解析完xml文件以后,基本上这个SqlSessionFactory对象也就构造完成了,那这个sqlSession对象自然而然也就生成了。

其他

其实如果你仔细看一下这个mybatis源码的结构,你会发现org.apache.ibatis.builder.xml这个包下就是各种xml文件完成解析的地方,而这个org.apache.ibatis.parsing包就是具体完成读取jie'xi解析xml的地方。
参考文章:mybatis深入浅出系列


0 0
原创粉丝点击