点击上方蓝色“方志朋”,选择“设为星标”

回复“666”获取独家整理的学习资料!

来源:cnblogs.com/scuury/p/10371246.html

近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。

核心部件:

  • SqlSession

  • Executor

  • StatementHandler

  • ParameterHandler

  • ResultSetHandler

  • TypeHandler

  • MappedStatement

  • Configuration

在分析工作原理之前,首先看一下我的mybatis全局配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 和spring整合后 environments配置将废除 --><environments default="development"><environment id="development"><!-- 使用jdbc事务管理 --><transactionManager type="JDBC" /><!-- 数据库连接池 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver" /><property name="url"value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" /><property name="username" value="root" /><property name="password" value="123456" /></dataSource></environment></environments><mappers><mapper  resource="sqlMapper/userMapper.xml"/></mappers>
</configuration>

第一步:创建一个sqlSessionFactory

在了解如何创建sqlSessionFactory之前,先看一下mybatis是如何加载全局配置文件,解析xml文件生成Configuration的

public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}
private void parseConfiguration(XNode root) {try {propertiesElement(root.evalNode("properties")); //issue #117 read properties firsttypeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));settingsElement(root.evalNode("settings"));environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

在上面的第二段代码中有一句

mapperElement(root.evalNode("mappers"));

刚好我们的全局配置文件中有一个mapper的配置,由此可见,mapperElemet()方法是解析mapper映射文件的,具体代码如下

private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {//进入该判断ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}

根据以上代码可以分析,在写mapper映射文件的地址时不仅可以写成resource,还可以写成url和mapperClass的形式,由于我们用的是resource,所以直接进入第一个判断,最后解析mapper映射文件的方法是

private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);}}

其中具体解析每一个sql语句节点的是

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

进入这个方法一层层深究,最后到这里可以知道MappedStatement是由builderAssistant(即MapperBuildAssistant)创建的。

public void parseStatementNode() {...builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

最后进入方法addMappedStatement(),mappedStatement最后以id为键保存在了Configuration中的一个map变量mappedStatements中。

public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);statementBuilder.resource(resource);statementBuilder.fetchSize(fetchSize);statementBuilder.statementType(statementType);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);statementBuilder.keyColumn(keyColumn);statementBuilder.databaseId(databaseId);statementBuilder.lang(lang);statementBuilder.resultOrdered(resultOrdered);statementBuilder.resulSets(resultSets);setStatementTimeout(timeout, statementBuilder);setStatementParameterMap(parameterMap, parameterType, statementBuilder);setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;}

最后回到我们的创建sqlSessionFactory上,之前的一切都是为了生成一个sqlSessionFactory服务的

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

从上面的代码可以看出最后是通过以Configuration为参数build()方法生成DefautSqlSessionFactory。

第二步:创建sqlSession

 public SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
//返回一个SqlSession,默认使用DefaultSqlSessionpublic DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {this.configuration = configuration;this.executor = executor;this.dirty = false;this.autoCommit = autoCommit;}

executor在这一步得到创建,具体的使用在下一步。

第三步:执行具体的sql请求

在我的代码里执行的是

User user = sqlSession.selectOne("test.findUserById", 1);

具体到里面的方法就是

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {//1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatementMappedStatement ms = configuration.getMappedStatement(statement);//2. 将查询任务委托给MyBatis 的执行器 ExecutorList<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);return result;} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

在这里通过statementId拿到了我们在第一步存在map里面的MappedStatement。在这里引用参考博客的一句话:

SqlSession根据Statement ID, 在mybatis配置对象Configuration中获取到对应的MappedStatement对象,然后调用mybatis执行器来执行具体的操作。

再继续看query()和queryFromDatabase()这两个方法

@SuppressWarnings("unchecked")public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) throw new ExecutorException("Executor was closed.");if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}deferredLoads.clear(); // issue #601if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {clearLocalCache(); // issue #482}}return list;}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

在这两个方法里面会为当前的查询创建一个缓存key,如果缓存中没有值,直接从数据库中读取,执行查询后将得到的list结果放入缓存之中。

紧接着看doQuery()在SimpleExecutor类中重写的方法

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(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}

Statement连接对象就是在这里创建的,因此Executor的作用之一就是创建Statement了,创建完后又把Statement丢给StatementHandler返回List查询结果。

接下来再看一下这里的两个方法prepareStatement()和query()的具体实现

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection);handler.parameterize(stmt);return stmt;}
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.<E> handleResultSets(ps);}

prepareStatement()是创建Statement的具体实现方法,调用parameterize()对创建的Statement对象设置参数,即为我们设为占位符的地方赋上指定的参数,parameterize()方法再深入进去就是调用ParameterHandler的setParameters()方法具体赋值了。

这里的query()是调用了ResultSetHandler的handleResultSets(Statement) 方法。作用就是把ResultSet结果集对象转换成List类型的集合。

总结以上步骤就是:

  1. 根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示

  2. 为当前的查询创建一个缓存Key

  3. 缓存中没有值,直接从数据库中读取数据

  4. 执行查询,返回List 结果,然后 将查询的结果放入缓存之中

  5. 根据既有的参数,创建StatementHandler对象来执行查询操作

  6. 将创建Statement传递给StatementHandler对象,调用parameterize()方法赋值

  7. 调用StatementHandler.query()方法,返回List结果集

总结

以上三个步骤所有流程大体可以用一张图来总结

热门内容:
  • 10大黑客专用的 Linux 操作系统,每个都很酷!

  • Spring官方都推荐使用的@Transactional事务,为啥我不建议使用!

  • Java生鲜电商平台-监控模块的设计与架构

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡

MyBatis 的工作原理,你了解过吗?相关推荐

  1. mybatis的工作原理

    MyBatis 的工作原理 在学习 MyBatis 程序之前,读者需要了解一下 MyBatis 工作原理,以便于理解程序.MyBatis 的工作原理如图 2 所示. 下面对图 2 中的每步流程进行说明 ...

  2. MyBatis基本工作原理介绍

    1.MyBatis基本工作原理介绍 计算机的基本工作就是存储和计算,而MyBatis是存储领域的利器.MyBatis的基本工作原理就是:先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结 ...

  3. Mybatis 的工作原理及流程

    1.介绍 MyBatis的底层操作封装了JDBC的API,MyBatis的工作原理以及核心流程与JDBC的使用步骤一脉相承,MyBatis的核心对象(SqlSession,Executor)与JDBC ...

  4. java的工作原理你知道吗_每天用Mybatis,但是Mybatis的工作原理你真的知道吗?

    近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了. 核心部件:SqlSession Executor Stat ...

  5. Mybatis的工作原理,你了解过吗?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:cnblogs.com/scuury/p/10371246. ...

  6. Mybatis 的工作原理,写得太好了!

    来源:cnblogs.com/scuury/p/10371246.html 近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边 ...

  7. mybatis工作原理_万字好文!MyBatis 的工作原理,你了解过吗?

    回复 1024 有特别礼包 作者:江南入直 | 来源:cnblogs.com/scuury/p/10371246.html 上一篇:微信支付的架构到底有多牛? 近来想写一个mybatis的分页插件,但 ...

  8. 五分钟,带你彻底掌握 MyBatis缓存 工作原理

    作者:双子孤狼 blog.csdn.net/zwx900102/article/details/108696005 前言 在计算机的世界中,缓存无处不在,操作系统有操作系统的缓存,数据库也会有数据库的 ...

  9. mybatis 原理_了解Mybatis的工作原理吗

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:江南入直 cnblogs.com/scuury/p/10371246.html 推荐 ...

最新文章

  1. 李飞飞公布谷歌云TPU、AutoML、行业方案等多项进展,AI云计算成谷歌全新增长点...
  2. 那些不被关注但很重要的html标签
  3. Java线程详解(14)-信号量
  4. (chap4 Http状态码) 5XX
  5. github使用个人总结
  6. 基于uniapp开发的适用于微信小程序,头条小程序
  7. ArrayList 的三种构造方法
  8. 5.2.6 std::atomic<>主要类的模板
  9. 重新排列数组的数,使得负数都排在正数的前面
  10. 【工业大数据】工业大数据应用场景分析;工业大数据,从何做起
  11. 动态背景线条,鼠标移动线条汇聚---背景特效
  12. Anaconda安装及使用
  13. 在衡量欧洲的政治意识形态时,调查规模的微小变化可能会很重要
  14. JavaWeb过滤器(Filter)
  15. 拜占庭将军问题OM算法详解(m=1,m=2)
  16. 【kimol君的无聊小发明】—用python写视频下载器
  17. MATLAB-基于灰色神经网络的预测算法研究(订单需求预测)
  18. 搜狗输入法输入,字母全是大写且之间有间隔
  19. shell脚本批量修改文件名
  20. python数字排序_python数字排序

热门文章

  1. 机器学习-----有监督,无监督,半监督学习的简单阐释
  2. 学习笔记53—Wilcoxon检验和Mann-whitney检验的区别
  3. 韦东山网课https://edu.csdn.net/course/play/207/1117
  4. Sublime遇见中文乱码问题?
  5. tomcat在服务器上改了8080的端口之后所带来的问题
  6. random类的使用
  7. 笔记 JVM调优流程
  8. wcf系列学习5天速成——第四天 wcf之分布式架构(转载)
  9. JAVA如何检测GC日志
  10. User Profile Data Web Part 读取属性字段