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该做的事。
上面的分析如果有出入,还望及时指出,互相学习,共勉互励!