Mybatis源码浅析

前言

最近准备看一看mybatis的源码,虽说使用了很久,但是里面的一些细节还是不算很了解,今天整理一个简单的文档。我们首先需要理解一件事,mybatis的底层使用的还是jdbc,所以如果对jdbc很熟悉的话,源码看起来就会很轻松;如果对jdbc不了解的话,建议先熟悉一下再使用mybatis

结构

首先奉上一张图,这张图能够简单的概述mybatis的结构,里面针对缓存等其他特性没有描述,后续深入了解后补上

这里涵盖了mybatis一次查询功能的主要类,如果有画错的地方,还请及时指出。

再附上一小段测试代码,方便解释上面的类图,只要跟着debug走一次就会豁然开朗

//重量级对象,最好保证单例
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(Resources.getResourceAsReader("mybatis-config.xml"));

//每次执行sql需要开启一个session回话,用完之后及时回收;考虑性能可以使用连接池,注意重复使用时数据冲突
SqlSession sqlSession = null;
try {
     sqlSession = factory.openSession();
     WxMenu menu = sqlSession.selectOne("xxx.yyy", 2);
     System.out.println(menu);
}catch (Exception e){
     e.printStackTrace();
}finally {
     if(sqlSession != null){
        sqlSession.close();
     }
 }

说明:上面的xxx.yyy分别代表mapper.xml中的namespace和sql的id属性值,在mybatis解析xml的时候加入缓存,用于唯一标识一个sql,大家测试的时候根据需要进行修改

流程

1)首先创建SqlSessionFactoryBuilder对象,通过名字就知道这个对象用于创建SqlSessionFactory。XMLConfigBuilder是用于读取并解析xml配置文件,并将配置信息存储在Configuration对象中,然后再通过Configuration对象构建SqlSessionFactory

2)SqlSessionFactory对象是一个重量级的对象,可以理解为与数据库连接的管理工厂,所有的会话(session)都从这里获取,所以最好做成单例的对象。配置文件的内容都是从官网粘贴的,官网:http://www.mybatis.org/mybatis-3/zh/getting-started.html

3)接下来就要从SqlSessionFactory获取session对象,每和数据库交互一次则需要开启一次会话,也就是SqlSession对象,包括事务控制(Transaction)、statement管理(MappedStatement,一个sql对应一个statement对象)、分页(RowBounds)、配置信息(Configuration)等对象都包含在其中

4)上面的一切都是准备工作,真正负责执行sql的是Executor。Executor的创建很有意思,默认情况下会用到SimpleExecutor和CachingExecutor。这两个类都实现了Executor,但是CachingExecutor做了一个小小的包装。这里贴上几行代码:

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

首先创建SimpleExecutor对象,然后再以此对象构建CachingExecutor(加粗部分)

5)当我们调用sqlseesion的查询方法时,其实调用的是executor的query()方法,最终调用doQuery方法进行查询。这里面有些缓存的操作,会涉及到lock,有兴趣的童鞋可以仔细看下。我们这里只观察查询数据库的情况。这里我们看一下doQuery方法的源码:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}

在这里获取到Configuration对象之后,再根据其他入参生成StatementHandler对象,仔细看下代码发现返回的是实现类PreparedStatementHandler(因为MappedStatement默认的statementType就是PREPARED)。PreparedStatementHandler也许大家比较陌生,但是熟悉jdbc的人一定知道PreparedStatement。回想一下jdbc执行的流程,获取connection对象,然后创建statement对象,执行statement.execute(sql),返回resultset对象,然后对结果进行处理。其实当你进入PreparedStatementHandler.query方法之后,你会发现和jdbc是一样的。我们看下这个query方法的代码:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
}

怎么样,和jdbc没什么差别吧。有的人肯定会问connection在哪里获取的,sql参数绑定在哪里处理的?看下上面的doQuery()方法中的prepareStatement()方法就知道啦,里面使用了parameterHandler做参数的绑定,具体代码就不在累述。

6)PreparedStatementHandler执行完之后,需要对结果处理,这里使用的是resultSetHandler,对结果处理完毕之后返回即可。

总结

当然我们这里只是简单看了一下mybatis的查询操作,其实mybatis还有其他特性我们没有分析。不过阅读源码没有必要一行一行的分析,挑选主要的类和方法,其他的高级特性可以按需再看。跟着debug走一次,心里有一个整体的流程会事半功倍的,还有多看注释(老外会在类和方法体上写很多注释)。mybatis的整体思想就是帮助我们构建sql,执行sql,然后再将结果映射到我们的model上,相当于是对jdbc的高级封装。其中的很多类的命名也很友好,比如PreparedStatementHandler其实就是PreparedStatement的的控制器,做了PreparedStatement该做的事。

上面的分析如果有出入,还望及时指出,互相学习,共勉互励!

posted @ 2018-12-15 16:27  _Emotion丶小寳  阅读(139)  评论(0编辑  收藏  举报