深入理解 MyBatis 启动流程(一)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 记录一下尝试阅读Mybatis源码的过程,这篇笔记是我一边读,一遍记录下来的,虽然内容也不多,对Mybatis整体的架构体系也没有摸的很清楚,起码也能把这个过程整理下来,这也是我比较喜欢的一种学习方式吧

环境简介与入口#


记录一下尝试阅读Mybatis源码的过程,这篇笔记是我一边读,一遍记录下来的,虽然内容也不多,对Mybatis整体的架构体系也没有摸的很清楚,起码也能把这个过程整理下来,这也是我比较喜欢的一种学习方式吧


单独Mybatis框架搭建的环境,没有和其他框架整合


入口点的源码如下:


@Test
public void test01() {
 try {
    this.resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    // 2. 创建SqlSessionFactory工厂
    this.factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 创建sqlSession
    // todo 怎么理解这个sqlSession? 首先它是线程级别的,线程不安全, 其次它里面封装了大量的CRUD的方法
    this.sqlSession = factory.openSession();
    IUserDao mapper = this.sqlSession.getMapper(IUserDao.class);
    List<User> all = mapper.findAll();
    for (User user : all) {
        System.out.println(user);
    }
    // 事务性的操作,自动提交
    this.sqlSession.commit();
    // 6, 释放资源
    this.sqlSession.close();
    this.resourceAsStream.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
}


构建SqlSessionFactory#


首先跟进这个,看看如何构建SqlSessionFactory对象


this.factory = new SqlSessionFactoryBuilder().build(resourceAsStream);


这个SqlSessionFactoryBuilder类的存在很简单,取名也叫他构建器,Mybatis的官网是这样解释它的,这个类可以被实例化(因为它有且仅有一个默认的无参构造),使用它的目的就是用来创建多个SqlSessionFactory实例,最好不要让他一直存在,进而保证所有用来解析xml的资源可以被释放


所以跳过对这个构建器的关注,转而看的build()方法

首先会来到这个方法,直接可以看到存在一个XML配置解析器,这其实并不意外,毕竟现在是MyBatis是孤军一人,就算我们使用的是注解开发模式,不也得存在主配置文件不是?


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


接着看看parser.parse(),它里面会解析主配置文件中的信息,解析哪些信息呢? 源码如下: 很清楚的看到,涵盖mybatis配置文件中的所有的标签


private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      ...
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }


第一个问题: 解析的配置信息存在哪里呢? 其实存放在一个叫Configuration的封装类中, 这个上面的解析器是XMLConfigBuilder 这个封装类的保存者是它的父类BaseBuilder


继续跟进build()方法,源码如下:


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


小结: 至此也就完成了DefaultSqlSessionFactory()的构建,回想下,这个构建器真的太无私了,牺牲了自己,不仅仅创建了默认的SqlSessionFactory,还将配置文件的信息给了他


打开SqlSession#


创建完成SqlSession工厂的创建, 我们继续跟进this.sqlSession = factory.openSession(); , 从工厂中获取一个SqlSession

跟进几个空壳方法,我们很快就能来到下面的方法:


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


什么是SqlSession呢? 注释是这么说的,他是MyBatis在java中主要干活的接口,通过这个接口,你可以执行命令(它里面定义了大量的 诸如selectList类似的方法),获取mapper,合并事务


The primary Java interface for working with MyBatis.
Through this interface you can execute commands, get mappers and manage transactions.


通过上面的我贴出来的函数,大家可以看到,通过事务工厂实例化了一个事物Transaction,那么问题来了,这个Transaction又是什么呢? 注释是这么解释的: Transaction 包装了一个数据库的连接,处理这个连接的生命周期,包含: 它的创建,准备 提交/回滚 和 关闭


紧接着创建执行器:configuration.newExecutor(tx, execType),默认创建的是CachingExecutor它维护了一个SimpleExecutor, 这个执行器的特点是 在每次执行完成后都会关闭 statement 对象


关于mybatis的执行器,其实挺多事的,打算专门写一篇笔记


继续看new DefaultSqlSession()我们得知,这个sqlSession的默认实现类是DefaultSqlSession


第三个参数autocommit为false, 这也是为什么我们如果不手动提交事务时,虽然测试会通过,但是事务不会被持久化的原因


小结: 当前函数是 openSession(), 如果说它是打开一个session,那跟没说是一样的,通过源码我们也看到了,这一步其实是Mybatis将 数据库连接,事务,执行器进行了一下封装然后返回给程序员


获取Mapper -- maperProxy#


我们交给mybatis的mapper是一个接口,看看Mybatis是如何实例化我们的结果,返回给我们一个代理对象的


跟进源码:IUserDao mapper = this.sqlSession.getMapper(IUserDao.class); ,经过一个空方法,我们进入Configuration类中的函数如下:


public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }


mapperRegistry中获取mapper,他是Configuration属性如下: 可以看到这个mapperRegistry甚至包含了Configuration,甚至还多了个 knownMappers

那么问题来了,这个knownMappers是干啥呢? 我直接说,这个map就是在上面解析xml配置文件时,存放程序员在<mappers>标签下配置的<maper>


protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 详情:
public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();


继续跟进这个getMapper()如下: 并且我们在配置文件中是这样配置的


<mappers>
    <mapper class="com.changwu.dao.IUserDao"/>
</mappers>


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


所以不难想象,我们肯定能获取到结果,通过上面的代码我们能看到,获取到的对象被强转成了MapperProxyFactory类型,它的主要成员如下: 说白了,这个 map代理工厂是个辅助对象,它是对程序员提供的mapper结果的描述,同时内置使用jdk动态代理的逻辑为mapper创建代理对象


public class MapperProxyFactory<T> {
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  ...
    protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }


说到了为mapper创建动态代理,就不得不去看看是哪个类充当了动态代理的需要的InvoketionHandler -- 这个类是mybatis中的MapperProxy, 没错它实现了InvocationHandler接口,重写了invoke()逻辑,源码如下:


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


小结: 至此当前模块的获取mapper对象已经完结了,我们明明白白的看到了MyBatis为我们的mapper使用jdk的动态代理创建出来代理对象, 这也是为什么我们免去了自己写实现类的粗活

相关文章
|
XML Java 数据库连接
【Spring Boot】使用MyBatis注解实现数据库操作
MyBatis还提供了注解的方式,相比XML的方式,注解的方式更加简单方便,无须创建XML配置文件。接下来好好研究注解的使用方式。
266 0
【Spring Boot】使用MyBatis注解实现数据库操作
|
SQL XML 缓存
mybatis执行流程
mybatis执行流程
123 0
|
SQL 缓存 Java
MyBatis执行流程
MyBatis执行流程
64 0
|
SQL XML 缓存
mybatis源码分析
基于xml构建mybatis的源码分析
|
存储 XML 安全
Spring - FactoryBean扩展实战_MyBatis-Spring 启动过程源码解读
在理解 MyBatis-Spring 的启动过程时,需要重点把握的是 `SqlSessionTemplate` 核心类的设计理念及其实现过程,使用了JDK动态代理机制。
169 0
Spring - FactoryBean扩展实战_MyBatis-Spring 启动过程源码解读
|
XML SQL 安全
|
SQL 存储 XML
MyBatis源码分析
源码分析MyBatis
331 0
|
设计模式 SQL 安全
MyBatis-整合Spring的原理分析
MyBatis-整合Spring的原理分析
MyBatis-整合Spring的原理分析
|
存储 XML SQL
MyBatis 与 Spring 整合原理分析
前言 我们常常将 Spring 与 MyBatis 结合在一起使用,由于篇幅问题,上篇《MyBatis 快速整合 Spring》仅介绍了将 MyBatis 整合到 Spring 的方式,这篇在上篇的基础上总结出几个问题,并尝试通过分析其底层源码进行回答。
225 0
MyBatis 与 Spring 整合原理分析
|
SQL Java 关系型数据库
SpringBoot 整合 Mybatis(注解方式)|学习笔记
快速学习 SpringBoot 整合 Mybatis(注解方式)
436 0
SpringBoot 整合 Mybatis(注解方式)|学习笔记