源码敲门砖-mybatis源码-入门1

简述:

背景:

大神指点学习spring源码前,最好的敲门砖就是spring的mybatis。因此我准备先学习mybatis源码。

Mybatis与 JDBC 编程的比较 :

1、通过配置文件可以灵活方便的创建连接池
2、sql也是在配置文件中,将sql与java代码分离。
3、传参和解析结果都可以直接映射到对应的java对象。

ORM:

ORM(Object Relational Mapping)框架采用元数据来描述对象与关系映射的细节,元数据一般采用XML格式,并且存放在专门的对象一映射文件中。
个人理解:就是通过ORM来完成数据库表和任何一种编程语言中描述对象的映射和操作,以此来灵活高效的操作数据。

什么是MyBatis?

mybais官网:https://mybatis.org/mybatis-3
MyBatis是非常主流的持久性框架,支持自定义SQL,存储过程和高级映射。MyBatis消除了几乎所有的JDBC代码以及参数的手动设置和结果检索。MyBatis可以使用简单的XML或注释进行配置,并将原始数据类型,映射接口和Java POJO(普通的旧Java对象)映射到数据库记录。

入门:

下载源码:

参考上一篇spring5准备里的方法,使用码云来下载源码。
下载mybatis前要下载好mybatis的pren项目,因为mybatis项目是依赖于它的。
https://github.com/mybatis/parent
https://github.com/mybatis/mybatis-3
在这里插入图片描述The MyBatis parent POM which has to be inherited by all MyBatis modules
所有mybatis的模块都要依赖于parent ,所以导入项目也要先导入parent 。

导入项目:

导入parent 项目后,执行maven clean install 。
存在一些警告,解决办法
在这里插入图片描述
新安装maven远程仓库没有指定为阿里云导致的,可以修改
maven目录/conf/settings.xml:
在这里插入图片描述

<mirror>      
		<id>alimaven</id>      
		<name>aliyun maven</name>      
		<url>http://maven.aliyun.com/nexus/content/groups/public/</url>      
		<mirrorOf>central</mirrorOf>        
</mirror>

pom.xml依赖爆红。

主流程代码:

//1.读取mybatis的主配置文件
InputStream inputStream = Resources.getResourceAsStream("resources/mybatis-config.xml");
//2.使用构建者模式,得到SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(inputStream);
//3.使用工厂模式,得到一个可以创建dao代理对象的SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.使用代理模式,使用SqlSession创建mapper的代理对象
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//5.使用代理对象执行操作:查询
List<Blog> blogs = mapper.selectOne(1);
System.out.println(blogs.get(0).getTitle());

1、读取mybatis的主配置文件源码路线:

InputStream inputStream = Resources.getResourceAsStream("resources/mybatis-config.xml");
底层流程:
-->Resources.getResourceAsStream("resources/mybatis-config.xml");
-->classLoaderWrapper.getResourceAsStream(resource, loader);
-->getResourceAsStream(String resource, ClassLoader[] classLoader)
-->ClassLoader.getResourceAsStream(resource);
-->URL url = getResource(name); 
return url != null ? url.openStream() : null;

这一步出现的问题:如果路径想直接写文件名,就出现下面的问题。
下载下来的源码结构是这样的,我在resource文件夹中写的主配置文件,text类写在org.apache.ibatis.dome下。
在这里插入图片描述原因:resources文件夹并不是Resources Root文件夹,需要改为Resources Root文件夹
在这里插入图片描述
因为resources文件夹在java下,所以无法被标记为,需要先将该文件夹移到与java文件夹同级,如下图,才可以解决
在这里插入图片描述

2、使用构建者模式,得到SqlSessionFactory对象

SqlSessionFactoryBuilder
SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(inputStream);
底层流程:
     SqlSessionFactoryBuilder:
     build(inputStream)
      -->build(inputStream, null, null);
      -->XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      -->build(new XMLConfigBuilder(inputStream, environment, properties).parse());
    
XMLConfigBuilder:
第一步很简单就是创建一下XMLConfigBuilder:
  -->this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    使用XPathParser来解析配置文件,获取所有的document
    this调用私有方法XMLConfigBuilder:
    new Configuration();
    Configuration:
    Configuration():为typeAliasRegistry的各个属性赋值。
  
第二步:使用XMLConfigBuilder进行解析:

XMLConfigBuilder:

public Configuration parse() {
    if (parsed) {//parsed在上一步构建的时候设置为false
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
      //每次使用就会设置为true,如果进来就是true代表已经解析过了,抛异常:每个XMLConfigBuilder只能使用一次
    }
    parsed = true;
    //首先解析的就是configuration标签,因此主配置文件也是以configuration为顶级标签,如下xml实例
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
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="jdbc.properties"></properties>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="mapper/BlogMapper.xml"/>
  </mappers>
</configuration>
里面的解析方法evalNode:解析节点
evaluate(String expression, Object item, QName returnType)
-->Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
-->XPathImpl类:getResultAsType根据传来的XPathConstants.NODE类型,知晓解析的是xml中的节点。来完成进一步解析,获取xml中的信息并将完成
Maps to Java {@link org.w3c.dom.Node}的转换。
将解析得到的信息封装到Configuration:XMLConfigBuilder类:
//根据这个方法,也就知道了,mybatis的配置文件标签顺序,尽量按下面的解析顺序来配置,否则会出现启动报错的问题,比如后面的标签内容需要去前面标签内容的值来作为自己的值 这样的情况
  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(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"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

xml配置顺序和简单实例:

<configuration>
  <properties resource="jdbc.properties"></properties>
  <settings><!--settings用来设置全局参数-->
  	<!--开启缓存-->
  	<setting name="cacheEnabled" value="true"/>
  </settings>
  <typeAliases>配置别名
    <typeAlias alias="role" type="com.*.类名">
  </typeAliases>
  <plugins>
	<!--在 MyBatis 中,对某种方法进行拦截调用的机制,被称为 plugin 插件-->
	<plugin interceptor="">
		<!--interceptor配置拦截器的类路径-->
	</plugin>
 </plugins>
  <objectFactory type=""></objectFactory>
  <objectWrapperFactory type=""/>
  <reflectorFactory type=""/>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <databaseIdProvider type=""></databaseIdProvider>
  <typeHandlers></typeHandlers>
  <mappers></mappers>
</configuration>
再次使用evalNode解析根据不同标签名,来获取相关信息。然后根据不同标签和每种标签对应的属性来解析。标签名为xxx,解析的方法就是xxxElement

例如properties节点的propertiesElement解析

String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
会判断路径是通过url还是相对路径resource来配置的,
是否合法等,来获取相应的资源。
最先获取解析的是properties,因此配置文件中properties标签要放第一个,
否则会导致后面的标签用到properties中的值时还没获取到值而报错。
这里我试了,如果你放到后面也会报语法错误。

再比如:environmentsElement(root.evalNode(“environments”));
获取environments的所有environment,然后读取environment的transactionManager和DataSource

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }

最终将xml解析的结果封装成configuration类。

第三步就是创建工厂:

用封装好的Configuration 创建DefaultSqlSessionFactory,再已多态的形式返回。DefaultSqlSessionFactory只有一个属性就是Configuration。

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

3、使用工厂模式,得到一个可以创建dao代理对象的SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();
底层调用的是

DefaultSqlSessionFactory:
@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
//从configuration获取数据源信息、事务配置、执行器类型创建Executor,并用这些信息创建DefaultSqlSession并返回
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      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();
    }
  }
  
Executor:后面再研究
//获取的处理器是默认的处理器:
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  //处理器分三种,
  public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

4、使用代理模式,使用SqlSession创建mapper的代理对象

BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

DefaultSqlSession类
 
 @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

MapperRegistry类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //获取指定mapper接口对应的代理类工厂
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
    //使用sqlSession的信息获取代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

最终调用工厂来获取mapper接口的动态代理对象

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

5.使用代理对象执行操作:查询

List blogs = mapper.selectOne(1);
System.out.println(blogs.get(0).getTitle());

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值