mybatis工作原理_万字好文!MyBatis 的工作原理,你了解过吗?
回复 1024 有特别礼包
作者:江南入直 | 来源:cnblogs.com/scuury/p/10371246.html
上一篇:微信支付的架构到底有多牛?
近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。
核心部件:
- SqlSession
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
- TypeHandler
- MappedStatement
- Configuration
在分析工作原理之前,首先看一下我的mybatis全局配置文件
<?xml version="1.0" encoding="UTF-8" ?>configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">default="development">"development">"JDBC" />"POOLED">"driver" value="com.mysql.jdbc.Driver" />"url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" />"username" value="root" />"password" value="123456" />"sqlMapper/userMapper.xml"/>
第一步:创建一个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 first typeAliasesElement(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 #631 databaseIdProviderElement(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服务的
搜索公众号顶级架构师回复关键字“offer”,获取一份算法面试题和答案惊喜礼包。
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,默认使用DefaultSqlSession public 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 List selectList(String statement, Object parameter, RowBounds rowBounds) {try {//1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement MappedStatement ms = configuration.getMappedStatement(statement);//2. 将查询任务委托给MyBatis 的执行器 Executor List 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 List 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 list;try { queryStack++; list = resultHandler == null ? (List) 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 List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List 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 List 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.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 List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute();return resultSetHandler. handleResultSets(ps); }
prepareStatement()是创建Statement的具体实现方法,调用parameterize()对创建的Statement对象设置参数,即为我们设为占位符的地方赋上指定的参数,parameterize()方法再深入进去就是调用ParameterHandler的setParameters()方法具体赋值了。
搜索公众号顶级架构师回复关键字“架构整洁”,获取一份惊喜礼包。
这里的query()是调用了ResultSetHandler的handleResultSets(Statement) 方法。作用就是把ResultSet结果集对象转换成List类型的集合。
总结以上步骤就是:
- 根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示
- 为当前的查询创建一个缓存Key
- 缓存中没有值,直接从数据库中读取数据
- 执行查询,返回List 结果,然后 将查询的结果放入缓存之中
- 根据既有的参数,创建StatementHandler对象来执行查询操作
- 将创建Statement传递给StatementHandler对象,调用parameterize()方法赋值
- 调用StatementHandler.query()方法,返回List结果集
总结
以上三个步骤所有流程大体可以用一张图来总结
PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。
大家一起在评论区聊聊呗~
公众号后台回复 架构 或者 架构整洁 有惊喜礼包!顶级架构师交流群
「顶级架构师」建立了读者架构师交流群,大家可以添加小编微信进行加群。欢迎有想法、乐于分享的架构师们一起交流学习。
扫描添加好友邀你进架构师群,加我时注明【姓名+公司+职位】
版权申明:内容来源网络,版权归原作者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。
猜你还想看
Java基于Swing和Netty仿QQ界面聊天小项目
图解 Docker 架构
Java压缩20M文件从30秒到1秒的优化过程
MyBatis 如何兼容所有日志框架?
mybatis工作原理_万字好文!MyBatis 的工作原理,你了解过吗?相关推荐
- mybatis是什么_深入解析:Mybatis接口没有实现类为什么可以执行增删改查?
作者:小傅哥 链接:https://segmentfault.com/a/1190000022767561 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言介绍 MyBatis 是一款非常优秀的 ...
- mybatis 连接池_应用框架之Mybatis数据源和连接池
本文将从以下几个方面介绍Mybatis的数据源和连接池: MyBatis数据源DataSource分类 数据源DataSource的创建过程 DataSource什么时候创建Connection对象 ...
- 自学嵌入式能找到工作吗_如何找到理想的嵌入式软件工作
自学嵌入式能找到工作吗 by Rohan Dasika 通过罗汉·达西卡(Rohan Dasika) 如何找到理想的嵌入式软件工作 (How to land your dream embedded s ...
- java断点续传原理_很简单的Java断点续传实现原理
原理解析 在开发当中,"断点续传"这种功能很实用和常见,听上去也是比较有"逼格"的感觉.所以通常我们都有兴趣去研究研究这种功能是如何实现的? 以Java来说,网 ...
- excel共享工作簿_在Excel中避免共享工作簿
excel共享工作簿 Occasionally a client asks me to create a shared workbook in Excel, so two or more employ ...
- mybatis 原理_图解源码 | MyBatis的Mapper原理
提到看源码,很多同学内心的恐惧的,其实这个从人性的角度来说是非常正常的,因为人们对未知的事物,都是非常恐惧的,其次,你内心可能始终觉得,好像不会原理也还是能工作啊,你的潜意识里没有强烈的欲望.从阅读源 ...
- 模板引擎工作原理_「白皮书解读搜索引擎的工作原理」如何排序
导读:搜索引擎是如何对网站进行排序的呢?今天解读的是索引. 本文解读的是:<百度官方课程检索排序> 1.检索排序原理 2.影响搜索结果排序的几个因素 一.检索排序原理 搜索引擎工作过程包括 ...
- ai人工智能换脸原理_他们如何看待AI监视内部工作原理
ai人工智能换脸原理 Large scale intelligent surveillance systems used by governments and corporates have attr ...
- 4个mos管驱动的全桥电路原理_最经典MOS管电路工作原理及详解没有之一
欢迎加入技术交流QQ群(2000人):电力电子技术与新能源 1105621549 高可靠新能源行业顶尖自媒体 在这里有电力电子.新能源干货.行业发展趋势分析.最新产品介绍.众多技术达人与您分享经验,欢 ...
最新文章
- CPU架构的llvm后端
- Spring Cloud 微服务实战笔记
- Python学习教程(Python学习路线):Python3之递归函数简单示例
- FPGA设计中RAM的一些基本概念
- 2013年下半年信息系统项目管理师考试试卷(回忆版)
- EntityFramework Core 2.0执行原始查询如何防止SQL注入?
- HTML列表、表格和媒体元素
- 原生js、jQuery实现选项卡功能
- 漫谈C++:良好的编程习惯与编程要点
- Asp.net Mvc使用PagedList分页
- 排列组合算法之二: 01转换法_java改变后的c++改进版
- C#2.0 泛型学习(入门)
- redis 任务队列
- 搭建属于自己的家庭私有云盘
- Objective C 的 private
- 生命力最强的新闻,放到二十年后依旧是新闻
- 中国八横八纵大容量光纤通信网——世界级光纤通信网
- 一串文字检测被删和被拉黑的好友!
- Vue3中如何进行页面局部刷新,组件刷新
- 没有良好数学基础,应该怎样学习人工智能?
热门文章
- Java基础学习-HelloWorld案例常见问题
- CentOS 系列安装 Docker
- 初创企业融资应量力而行
- 20140418--第1讲.开山篇
- 《CLIP2Video》-腾讯PCG提出CLIP2Video,基于CLIP解决视频文本检索问题,性能SOTA!代码已开源!...
- ICCV2021 MuST:还在特定任务里为刷点而苦苦挣扎?谷歌的大佬们都已经开始玩多任务训练了...
- 企业级OpenCV、图像识别资料免费下载,仅此1天!
- 60分钟入门PyTorch,官方教程手把手教你训练第一个深度学习模型
- 【TensorFlow】TensorFlow从浅入深系列之十 -- 教你认识卷积神经网络的基本网路结构及其与全连接神经网络的差异
- 图机器学习 | 图信号处理、矩阵分解、随机游走和深度学习算法