Mybatis执行流程浅析(附深度文章推荐&面试题集锦)

首先推荐一个简单的Mybatis原理视频教程,可以作为入门教程进行学习:点我 (该教程讲解的是如何手写简易版Mybatis)

执行流程的理解

理解Mybatis的简单流程后自己手写一个,可以解决百分之70的面试问题和开发中遇到的困惑,此乃重中之重

假如我们要自己设计一个半自动的仿Mybatis框架,有哪些环节是必不可少的呢?思考再三,必然有以下环节:

  • 相关配置文件加载(XML类型,接口类型则可以省略)
  • 接口代理(JDK 动态代理)
  • 针对XML或者接口进行解析 ==》即把不可直接执行的SQL处理为携带参数,返回值明确的数据结构
  • JDBC模块执行,并返回对应的返回值类型

如果仅考虑这三点的话,其实实现一个简单的ORM框架就很容易了,再附加一些反射和正则表达式等等就可以搞定了.

那如果去参考Mybatis,我们来看看它的几个环节是如何设计的:

Mybatis执行流程浅析(附深度文章推荐&面试题集锦)_第1张图片
图片

其实大致思路一样,需要一个数据结构去存储全部的变量,通过接口代理的方式调用Sqlsession里面内置的方法,不同的是真正的执行者又加了一层,是 Executor,再通过原始JDBC返回数据给调用者,当然,真正的Mybatis包含了众多的设计模式以及数据源,缓存,动态SQL,数据库事务,延迟加载处理等等

为了验证mybatis的执行流程,采用了两种方式去调用接口,如下所示:

public static void main(String[] args) throws IOException {
    // 指定全局配置文件
    String resource = "mybatis-config.xml";

    // 读取配置文件
    InputStream inputStream = Resources.getResourceAsStream(resource);

    // 构建sqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession = sqlSessionFactory.openSession();

    // Mapper 编程方式
    ScriptDirDao mapper = sqlSession.getMapper(ScriptDirDao.class);
    System.out.println(mapper.selectOne(1));

    // ibatis 编程方式 ---> 注意由于没有显式设置提交, 因此两个sql执行使用的是同一次sqlsession, 即默认触发了一级缓存
    Object object = sqlSession.selectOne("com.mycode.mybatis.ScriptDirDao.selectOne", 1);
    System.out.println(object);
}

ibatis编程方式实际就是通过 namespace+方法名定位具体的接口方法,然后传递参数并执行

正常使用方式就是基于上述的基本流程做了一层自动的返回值映射,接口方法的匹配

这里有个小点需要强调下,真正的执行者是Executor,我们每次在使用以下代码:

// 构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession sqlSession = sqlSessionFactory.openSession();

// Mapper 编程方式
ScriptDirDao mapper = sqlSession.getMapper(ScriptDirDao.class);
System.out.println(mapper.selectOne(1));

通过查看源码也可以看到,SqlSession接口的默认实现类是DefaultSqlSession

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;  // 执行者

  private final boolean autoCommit;
  private boolean dirty;
  private List> cursorList;
  .......
}

而方法真正的执行,如selectList方法:

@Override
public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

深度分析文章参考

源码分析Mybatis MapperProxy初始化之Mapper对象的扫描与构建 (作者:掘金- 中间件兴趣圈)

源码分析Mybatis MappedStatement的创建流程(作者:掘金- 中间件兴趣圈)

Mybatis执行SQL的4大基础组件详解(作者:掘金- 中间件兴趣圈)

源码解析MyBatis Sharding-Jdbc SQL语句执行流程详解(作者:掘金- 中间件兴趣圈)

mybatis 一级二级缓存原理及使用建议(美团技术团队-官方博客)

面试题集锦

Myabtis的细节使用和执行原理其实都很好理解,对于源码感兴趣的可以深挖,但大多时候建议点到为止即可

还是着眼当下 面向面试要点进行针对性学习(不包括一些简单的使用问题

#{}和${}的区别是什么?

#{} 是预编译处理,${}是字符串替换。Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的
set 方法来赋值;
Mybatis 在处理时,就是把时,就是把{}替换成变量的值。
使用#{}可以有效的防止 SQL 注入,提高系统安全性

PS:mybatis执行的本质还是SQL,因此回归本质可以简单理解为一个对于PreparedStatement ,一个对应 Statement

通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?(id是否可以相同)

Dao 接口即 Mapper 接口,接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数

实现原理:Mapper接口的工作原理是JDK动态代理,mybatis会对每一个mapper代理生成一个mapperProxy对象,代理对象会拦截接口方法,转而自动对应到sqlsession上,最终由Executor执行

参数不同,方法不可重载,为什么?

上文说到mybatis有一个环节是解析XML文件或者解析接口,它会去构建一个叫做 MapperStatement 对象去存储mapper的相关信息,每一个dao接口方法在执行的时候到底是如何定位找到对应的MapperStatement 的呢?

源码逻辑图:

Mybatis执行流程浅析(附深度文章推荐&面试题集锦)_第2张图片
1
Mybatis执行流程浅析(附深度文章推荐&面试题集锦)_第3张图片
2
// 这个 mappedStatements 即
protected final Map mappedStatements = new StrictMap("Mapped Statements collection");

// Key即 XML文件中配置的