在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手:

写给mybatis小白的入门指南

mybatis底层原理学习(一):SqlSessionFactory和SqlSession的创建过程

如果你想获得更好的阅读体验,可以点击这里:Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

(1)在使用Mybatis操作数据库的时候,每一次的CRUD操作都会去获取一次映射配置文件(mapper xml文件)对应的sql映射。每一个sql映射在内存缓存中(创建SqlSessionFactory之前就缓存在内存中了)都会有唯一ID,就是sql映射所在xml文件的命名空间加上sql映射配置节点的id值。
(2)Mapper xml文件的命名空间使用的是类的全路径名,这样做的好处是可以全局唯一,又可以通过反射获取对应的Mapper类。可以理解成每一个mapper xml文件对应一个Mapper类。
(3)mapper xml文件每一个sql映射节点的id属性值对应类的一个方法。我们在配置sql映射的时候也必须这样做,因为Mybatis的底层就是使用反射机制来获取执行方法的全路径作为ID来获取sql的映射配置的。
(4)每一个和mapper xml文件关联的类,都是Mapper类,在执行过程,通过动态代理,执行对应的方法。Mybatis是如何判断哪些类是Mapper类的呢?其实只有在运行时才会知道。在加载Mybatis配置文件中,通过解析mapper xml文件缓存了所有的sql映射配置,在调用SqlSession的getMapper方法获取Mapper类的时候才会生成代理类。

现在,我们来从源码角度分析Mapper代理类的创建过程,demo源码在后面给出 demo示例:

public class Main {private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);public static void main(String[] args) {SqlSession sqlSession = MyBatisUtil.getSqlSession();ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class);Article article = mapper.selectOne(1);LOGGER.info("title:" + article.getTitle() + " " + "content:" + article.getContent());}}复制代码

我们在这行代码处搭上断点:

ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class);
复制代码

Debug进去,执行下面代码:

public <T> T getMapper(Class<T> type) {return configuration.<T>getMapper(type, this);}
复制代码

configuration持有Mybatis的基本配置信息,继续看看getMapper方法的执行:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}
复制代码

mapperRegistry缓存了所有的SQL映射配置信息,在加载解析Mybatis配置文件(例子是mybatis)和mapper xml文件的时候完成缓存的,继续看getMapper的执行:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {// 这里首先会获取Mapper代理类工厂,拿到代理工厂就创建代理类final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {// 创建Mapper代理类return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}
复制代码

通过动态代理机制创建Mapper代理类

protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}
复制代码

到这里,动态代理类创建完成。 通过分析了源码执行过程,Mapper代理类的创建过程弄清楚了,大体就是通过从缓存中获取sql映射配置的id(类全路径名+方法名)来通过动态代理机制创建代理类,实际执行的CRUD是执行动态代理类的方法。 执行CRUD操作的时候,我们都会执行到动态代理类的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}
复制代码

最后找到映射的方法,执行mapperMethod.execute(sqlSession, args)。 通过代码我们可以看到,会根据执行方法的操作类型(CRUD)执行不同的逻辑处理。

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;}
复制代码

我们分析一下查询select:

if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}
复制代码

首先根据方法返回类型的不同执行不同的逻辑,最终会调用SqlSession的selectXXX方法,

public <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List<T> list = this.<T>selectList(statement, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}
复制代码

List<T> list = this.<T>selectList(statement, parameter);这行代码逻辑处理:

public <E> List<E> selectList(String statement, Object parameter) {return this.selectList(statement, parameter, RowBounds.DEFAULT);}
复制代码

继续进去:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
复制代码

到这一步,是调用执行器Executor的query方法:

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}复制代码

进去query方法:

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {Cache cache = ms.getCache();if (cache != null) {flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, parameterObject, boundSql);@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}
复制代码

继续进去query方法:

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();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}
复制代码

真正访问数据库的是这行代码:list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

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;}
复制代码

查询操作由doQuery方法处理,这段代码就接近原生JDBC操作了,首先会获取语句处理器,然后开始执行语句,执行完,还会对结果进行结果集处理,返回处理的结果集,这里就不多分析了

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);}}
复制代码

我们在使用Mybatis进行CRUD操作的时候,大体过程是这样:

  • 解析基本配置文件和Sql映射配置文件(mapper xml)文件,缓存配置文件节点内容在内存(一般此步骤只会执行一次,多次调用都会复用缓存结果)
  • 获取SqlSession,通过SqlSession来获取Mapper类,生成Mapper类的代理
  • 执行CRUED操作

当然,这个过程Mybatis还做了很多事情,Sql的解析,结果集的处理……等操作我们在这篇文章不分析,后面会有文章分析。这篇文章目的是分析Mapper代理类的创建过程和简单分析一个查询操作的过程。

源码地址

学习更多源码分析文章,欢迎关注微信公众号:深夜程猿
【福利】关注公众号回复关键字,还可获得视频学习资源,求职简历模板

Mybatis底层原理学习(二):从源码角度分析一次查询操作过程相关推荐

  1. Adroid学习之 从源码角度分析-禁止使用回退按钮方案

    有时候,不能让用户进行回退操作,如何处理? 查看返回键触发了哪些方法.在打开程序后把这个方法禁止了. 问题:程序在后台驻留,这样就会出现,其他时候也不能使用回退按钮.如何处理,在onpase()时方法 ...

  2. ❤️缓存集合(一级缓存、二级缓存、缓存原理以及自定义缓存—源码+图文分析,建议收藏) ❤️

    ❤️缓存集合(一级缓存.二级缓存.缓存原理以及自定义缓存-源码+图文分析,建议收藏) ❤️ 查询 : 连接数据库 ,耗资源!一次查询的结果,给他暂存在一个可以直接取到的地方!--> 内存 : 缓 ...

  3. mvcc原理_MVCC原理探究及MySQL源码实现分析

    沃趣科技数据库专家  董红禹 MVCC原理探究及MySQL源码实现分析 数据库多版本读场景 session 1 session 2 select a from test; return a = 10 ...

  4. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  5. 从源码角度分析MapReduce的map-output流程

    文章目录 前言 流程图 源码分析 1 runNewMapper方法 2.NewOutputCollector方法 2.1 createSortingCollector方法 2.1.1 collecto ...

  6. 从源码角度分析 Mybatis 工作原理

    作者:vivo互联网服务器团队-Zhang Peng 一.MyBatis 完整示例 这里,我将以一个入门级的示例来演示 MyBatis 是如何工作的. 注:本文后面章节中的原理.源码部分也将基于这个示 ...

  7. Golang源码学习(二)----Go源码学习基础

    ### 本文源码版本为 GO 1.17.8 Windows/amd64: ### 可能参与对比的版本:GO 1.16.2 Linux/amd64一.Golang的编译器究竟是如何工作的? (学习源码有 ...

  8. Android开发知识(二十三)从源码角度分析ListView的滑动复用机制

    文章目录 前言 认识RecycleBin机制 ListView的布局方式 ListView的元素创建流程 ListView滑动加载过程 前言 ListView作为一个常用的列表控件,虽然现在基本被Re ...

  9. Hadoop(十二):从源码角度分析Hadoo是如何将作业提交给集群的

    为什么80%的码农都做不了架构师?>>>    一:MapReduce提交作业过程的流程图 通过图可知主要有三个部分,即: 1) JobClient:作业客户端. 2) JobTra ...

最新文章

  1. spring cloud微服务分布式云架构--hystrix的使用
  2. 计算机硬件类 计算机网络基础,计算机硬件类计算机网络基础1.doc
  3. JS与OC中的方法相互调用
  4. excel修改列名 pandas_P9:pythonpandas玩转excel文件
  5. 哈希表及哈希表查找相关概念(转)
  6. java文件损坏_java – 损坏的文件处理
  7. Springboot 2.0.0单元测试
  8. 触发父组件变量_Vue组件之间的传值
  9. 设置Markdown中展示Liquid(Jekyll)但不解析的方式
  10. java折半查找(递归版)
  11. lvremove 删除逻辑卷
  12. 基于SSM实现在线考试及题库管理系统
  13. html实现点击下载文件
  14. three.js 05-01 之 PlaneGeometry 几何体
  15. 阿里云服务器部署项目邮箱发送功能465端口报错
  16. 手机闪存速度测试工具,AndroBench
  17. 计算机什么行不变裂变的知识,科普知识竞赛参考题目
  18. WIN10 启动后花屏
  19. freemarker生成word之后遇到未解决的问题,希望有大佬赐教!!!
  20. MS SQL Server数据库修复利器—D-Recovery For MS SQL Server数据恢复软件

热门文章

  1. SAP QM初阶之物料主数据QM视图里的Preferred Inpspection Type
  2. 47%德国企业认为人工智能较大的优势是提高生产效率
  3. 2018年中国城市用电量30强
  4. 在ML中缺乏数据可是个大问题,亲测有效的5种方法帮您解决
  5. 生成器与迭代器的区别
  6. SAP MM 如下图,做发票校验的时候,对于非计划交货成本分摊到各个ITEM中,为什么分摊比例是1:2,而非1:6?
  7. 言论丨李开复:中国在AI领域的优势与机会,现阶段AI领域的挑战
  8. 汪劲:生命系统中的非平衡物理学
  9. AI如何帮助我们理解意识——麻省理工最新大脑研究
  10. 原创工作发表难之叶公好龙