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深入浅出系列
- mybatis源码DEBUG学习
- mybatis源码DEBUG学习2
- Mybatis源码学习
- MyBatis开始学习源码
- mybatis源码学习
- mybatis学习(1)---导入mybatis源码
- myBatis源码学习之SqlSessionFactoryBuilder
- myBatis源码学习之SqlSessionFactory
- myBatis源码学习之SqlSession
- Mybatis源码学习之TypeHandler
- 学习mybatis源码疑惑一
- Mybatis源码学习(一)-整体框架理解
- MyBatis Migrations 源码篇 学习笔记
- Mybatis源码学习(一)包结构
- Mybatis源码学习(二)annotation包
- Mybatis源码学习(三)cache包
- Mybatis-generator源码学习和修改
- mybatis源码学习之基础工程准备
- 跪求高手解答!Fragment布局中的View概率性空指针
- MongoDB学习(一)简介、安装与启动
- android使用百度导航SDK1.1.0实现导航功能。怎么才能实时更新我的位置
- JAVA百科---[基础篇]环境变量配置
- tabbaritem的问题
- mybatis源码DEBUG学习
- 前端技术学习之选择器(五)
- 图片
- unity3d ios开发 调试的流程?
- PAT 乙级 1065. 单身狗(25)
- JSP页面技术之EL表达式
- 关于onCreateOptionsMenu的布局问题
- webView打开的页面和手机浏览器打开的不一样
- MarkdownPad2.5 注册码