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

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

执行流程的理解


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


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



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


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


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


其实大致思路一样,需要一个数据结构去存储全部的变量,通过接口代理的方式调用 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();
    }
}

面试题集锦


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 执行流程浅析(附深度文章推荐 & 面试题集锦)


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

// 这个 mappedStatements 即
protected final Map mappedStatements = new StrictMap("Mapped Statements collection");

// Key即 XML文件中配置的