Mybatis的 SqlSessionFactory 初始化过程 和SqlSession 初始化过程

用了几年的Mybatis,但是一直没有时间去研究下这个框架,当然这段时间也找了事件大概的看了一遍Mybatis主要功能的源码。
总体上感觉Mybatis 属于小巧功能却十分强大的框架,个人以为Mybatis应该作为Java初学者第一个阅读的框架源码。有兴趣可以跟着博主思路读读源码>-<

本文将以以下几个问题展开:

  1. Mybatis 运行机制是怎样的?
  2. Mybatis 初始化中,如何获取SqlSessionFactory的?
  3. Mybatis 中,各大组件是通过怎样的形式串起来的?
  4. JDBC 的几种事务级别都各是什么?
  5. 创建 SqlSession时候,有哪些参数可以传?
  6. Mybatis有哪些事务类型?

简介

MyBatis 是一款基于Java的优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

使用上官网并没有提供太多samples,不过提供了很多配置意思,这个可以帮助我们在一些情况下优化代码及减少一些bug。

https://mybatis.org/mybatis-3/zh/index.html

例子

以API 方式作为入口去了解框架运行原理是一种非常好的方式,本文例子以官网例子变形作为分析的起点:

    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.getByIndex(11);
            log.info("user :{}", user);
        }
    }

相关xml配置:

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1/df?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

具体例子可以看 https://github.com/anLA7856/mybatislearn

通过InputStream 获取到指定输入流的xml配置文件后,则进入了 SqlSessionFactory 的表演时间。

SqlSessionFactorySqlSessionFactoryBuild 构建,SqlSessionFactoryBuild 中有多个不同的build方法,主要有三种类型参数:

  1. Reader
  2. String enviroment
  3. Properties properties
  4. InputStream
  5. Configuration

InputStreamReader指传入是字节流还是字符流去解析不同格式的输入流信息,而 enviromentproperties 则是传入解析的具体配置,可以指定在xml中只返回哪个环境(enviroment) 的配置,或者完全覆盖属性(properties)。而Configuration则是Mybatis的核心对象,整个解析过程出的数据都会放入到Configuration中。

在选择完方法之后,Mybatis就会进入到自己实现的XML解析类XMLConfigBuilder 中,通过XPathParser 作为具体解析器,将解析出的xml存储在org.w3c.dom.Document中:

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

XMLConfigBuilder 父类 BaseBuilder 中,有三个变量:

  1. Configuration configuration:Mybatis中核心配置中心
  2. TypeAliasRegistry typeAliasRegistry:别名配置中心,以Map<String, Class<?>> 存储Mybatis配置别名和类对象映射,例如:registerAlias("byte", Byte.class);
  3. TypeHandlerRegistry typeHandlerRegistry:类型处理配置中心,即值该怎样传递给JDBC,又怎样从JDBC中拿出来?以 Map<JdbcType, TypeHandler<?>> 存储。例如Java对象String,对应数据库可以有多种数据类型varchar, longvarchar, nchar 等。二者交互就是通过TypeHandler来处理的。

当 通过 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 解析后,开始由 build(parser.parse()); 将解析后xml依据Mybatis需要组装起来。

Configuration

整个SqlSessionFactory 初始化过程,在我看来一句话描述就是:
Mybatis通过 其内部定义的强大的XML解析,将配置的xml文件和Mapper文件全部解析成为Java类对象形式,并且以Configuration为中心。

当然里面的配置以及存储对象很多。

根据上面源码一路往下,进入到XMLConfigBuilderparseConfiguration方法:

 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 节点内容,而后根据节点对Configuration 相关属性进行填充:

  1. 读取properties 内容,将其放入到ConfigurationProperties variables 中。
  2. 读取settings内容,如果有则放到 ConfigurationClass<? extends VFS> vfsImpl ,并且会加入到VFSVFS.addImplClass(this.vfsImpl);
    VFS 是mybatis提供的一个接口,用于在服务器中访问资源,即可以利用vfs访问内部文件
  3. 读取settings内容,读取logImpl 配置,将 其设置到 ConfigurationlogImpl ,以及使用 初始化 LogFactory 的用户配置日志工厂LogFactory.useCustomLogging(this.logImpl);
  4. 读取别名配置,嚷道 ConfigurationTypeAliasRegistry 中。
  5. 读取 拦截器配置,放入 ConfigurationInterceptorChain
  6. 配置ObjectFactory,并放入Configuration,Mybatis将会用它来创建所有的需要的Object
  7. 配置 ObjectWrapperFactory,Mybatis没有默认实现。博主暂时没发现其用途,因为Mybatis默认实现比较简单,hasWrapperFor 返回false,而getWrapperFor 则抛出了异常。
  8. 配置 ReflectorFactory ,用于作用反射工厂
  9. 读取并存储所有settings节点内容到 Configuration的不同配置中
  10. 配置事务工厂TransactionFactory以及数据源工厂 DataSourceFactory
  11. 配置databaseIdProvider,同样存储到 Configuration
  12. 配置 typeHandlers,放入到 Configuration
  13. 配置mappers 节点,加载所有的mappers的xml配置。
  • 使用 XMLMapperBuilder 加载 对应mappers配置
  • 使用 Set<String> 来存储已经加载过的配置
  • 使用 MapperBuilderAssistant 保存 缓存,或者使用别处或者当前自己缓存,以命名空间id为标识,或者是ParameterMapping,最后交由的都是Configuration 保管。
  • 解析 cache、/mapper/parameterMap、/mapper/resultMap、/mapper/sql、select|insert|update|delete 等内容
  • 解析完成后,存储 Set<String> 类型的 loadedResources 和 将 MapperRegistry 存入 mapperRegistry 中。

SqlSession 初始化过程

读取完配置,构建完成 SqlSessionFactory ,就到了SqlSession 的构造事件,SqlSessionFactory 提供了以下几种获取方法:

  • public SqlSession openSession()
  • public SqlSession openSession(boolean autoCommit)
  • public SqlSession openSession(boolean autoCommit)
  • public SqlSession openSession(ExecutorType execType)

  • openSession基于三个参数的多个重载方法:
  • ExecutorType:执行方式,分为 SIMPLE, REUSE, BATCH
  • TransactionIsolationLevel level:设置事务级别NONE、READ_COMMITTED、READ_UNCOMMITTED、REPEATABLE_READ、SERIALIZABLE
  • autoCommit:设置自动提交,分truefalse
  1. ExecutorType,默认是 SIMPLE,
  • SIMPLE:即每次用完Statement 后,都会关闭Statement,而后下次由重新打开
  • REUSE: 会将 Statement 存储到 Map<String, Statement> 中,keyboundSql.getSql()
  • BATCH:这种执行方式,主要用于批量操作,每次执行将statement预存到有序集合,主要用于循环或者多次执行构建一个存储过程或批处理过程
  1. TransactionIsolationLevel,事务级别,对应jdbc规范中的集中食物级别
  • NONE:没有事务,对应 JDBC中 Connection.TRANSACTION_NONE
  • READ_COMMITTED: 禁止脏读,允许不可重复读和幻读。读已提交的,禁止事务读未提交行。会对相应的行加索,如果是范围读取例如i>100,InnoDB使用间隙锁或下一键(间隙加索引记录)锁来锁定扫描的索引范围,以阻止其他会话插入范围所涵盖的间隙。(会出现不可重复读,例如此时其他session commit了一次操作,那么当前事务还是能够读取到)
  • READ_UNCOMMITTED:允许脏读,不可重复读和幻读。一个事务可以读取到另一个事务未提交的行(脏读)。如果另一个事务回滚,则当前事务则会读到一个不存在的行(风险)。非锁定方式。
  • REPEATABLE_READ:禁止脏读和不可重复读,允许幻读。这是Innodb默认事务级别。不允许出现以下情况:
    i:事务读取一个未提交的更改行
    ii:同样不允许当一个事务读取行而另一个事务尝试修改行
    iii:禁止一个事务两次读取获取不同结果(不可重复读)
    理解上来说,当有A,B事务,当B事务设为REPEATABLE_READ时,那么B这个事务中,不管查询多少次某一个内容,都是一样的。
    无论是否其他事务对其已经做了提交性的更改。Innodb对其处理是后面查询都只读第一次的快照。(这样会出现幻读)
  • SERIALIZABLE :禁止脏读,不可重复读和幻读。最严格的。
  • Mysql可以使用 select @@tx_isolation; 查询当前事务隔离级别。
  • 设置事务隔离级别:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 如果自动提交,那么每一行语句都会自动提交,Mysql中查看方式为:select @@autocommit;,关闭当前session的自动提交为:
    set autocommit = off;

下面看 DefaultSqlSessionFactoryopenSessionFromDataSource

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

上面代码有以下几种意思:

  1. 通过Environment获取事务类型,Mybatis有两种事务提供:
  • JdbcTransaction,使用Jdbc的事务控制,就是上面讲的四种事务隔离级别,并且直接通过使用JDBC的commit和rollback功能,延迟获取连接知道有需要。当设定了 autocommit,那么会忽略commitrollback
  • ManagedTransaction:让容器管理整个事务的生命周期,延迟获取连接。忽略所有的 commit和rollback请求。
  1. newExecutor 中,使用事务创建一个执行器,默认的ExecutorTypeSIMPLE 类型,如果没有更改默认开启的二级缓存,则会将所有的执行器都包装在一个
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }

CachingExecutor里面。

  • 一级缓存是指 在单个SqlSession中的缓存,可以在
  • 二级缓存是可以跨SqlSession 共享的缓存
  • Mybatis中获取操作为 二级缓存->一级缓存->数据库
  • 其实最好不要在Mybatis中开启缓存,使用它作为纯粹的ORM框架就好了
    而后,会将所有配置的过滤器串成链:
executor = (Executor) interceptorChain.pluginAll(executor);

遍历所有interceptors,而后拿到属于 Executor 的过滤器链,并封装返回:


  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
  1. 将获取的Executor 包装称为一个DefaultSqlSession 并返回。

整个 SqlSessionFactory 初始化过程 和SqlSession 初始化过程 已分析完。
下篇文章将开始围绕Mysql插件以及研究Mysql一级、二级缓存相关>-<

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Mybatis:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值