• 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该做的事。

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

  • 相关阅读:
    [转]深度理解依赖注入(Dependence Injection)
    [转]控制反转(IOC)和依赖注入(DI)
    [转]依赖注入的概念
    [转]struct实例字段的内存布局(Layout)和大小(Size)
    异步编程模式
    HTTP协议返回代码含义
    [转]StructLayout特性
    Stack的三种含义
    FineUI登入的例子中遇到的一些问题
    编程以外积累: 如何给项目生成类似VS2008的说明文档
  • 原文地址:https://www.cnblogs.com/1ning/p/10123763.html
Copyright © 2020-2023  润新知