1.初始化Configuration对象

mybatis-config.xml这个文件主要是用于配置数据源、配置别名、加载mapper.xml。

mapper标签所指向的路径就是另外一个xml文件:xxxMapper.xml,而这个文件中写了我们查询数据库所用的SQL。

MyBatis实际上就是将这两个xml文件,用XMLConfigBuilder的parse()方法解析成配置对象

Configuration的核心配置类:

Environment environment:数据库环境
MapperRegistry mapperRegistry:保存解析到的所有映射器(mapper接口+mapper.xml)
Map<String, MappedStatement> mappedStatements:保存整个项目所有的SQL语句
Map<String, ResultMap> resultMaps:保存mapper.xml中配置的<resultMap>
Set<String> loadedResources:保存扫描到的所有的资源

点进去parse()方法,用XPath语法,获取核心配置的根节点

​​ public Configuration parse() {//查看该文件是否已经解析过if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}//如果没有解析过,则继续往下解析,并且将标识符置为trueparsed = true;//解析<configuration>节点parseConfiguration(parser.evalNode("/configuration"));//跟着configuration节点去解析它的子节点return configuration;}
点进去parseConfiguration()方法解析根节点下的子节点
private void parseConfiguration(XNode root) {try {//解析<Configuration>下的节点//issue #117 read properties first//<properties>propertiesElement(root.evalNode("properties"));//<settings>Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);//别名<typeAliases>解析typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631//配置环境environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));//主要 <mappers> 指向我们存放SQL的xxxxMapper.xml文件mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

每个解析不同标签的方法内部都对Configuration对象进行了set或者其它类似的操作,如:

private void propertiesElement(XNode context) throws Exception { //引入包含数据库连接参数的database.properties文件的细节if (context != null) {  //如果连接数据库参数的文件不为空Properties defaults = context.getChildrenAsProperties();  //获取连接数据库文件的属性String resource = context.getStringAttribute("resource");String url = context.getStringAttribute("url");if (resource != null && url != null) {throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");}if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}Properties vars = configuration.getVariables();if (vars != null) {defaults.putAll(vars);}parser.setVariables(defaults);configuration.setVariables(defaults);}}

这里由于代码量比较大,而且大多数构建都是些细节,大概知道怎么用就可以了,就不在文章中一一说明了

解析mapper.xml里面的节点

​​//解析mapper文件里面的节点private void configurationElement(XNode context) {try {//获取命名空间 namespace,后期mybatis会通过这个动态代理我们的Mapper接口String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {//如果namespace为空则抛一个异常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"));//解析增删改查节点<select> <insert> <update> <delete>buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}}

2.构建SqlSessionFactory

我们现在得到了一个SqlSessionFactory对象,下一步就是要去获取SqlSession对象,这里会调用SqlSessionFactory.openSession()方法来获取,而openSession中实际上就是对SqlSession做了进一步的加工封装,包括增加了事务、执行器等。

​​ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {//对SqlSession对象进行进一步加工封装final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);//构建SqlSession对象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();}}

SqlSessionFactory对象中注入Configuration对象,所以它保存了全局配置信息,以及初始化环境和DataSource,当我们调用openSession方法时,交给SqlSession来对数据库做相关操作

3.打开SqlSession会话

现在我们获取到了一个SqlSession对象之后打开会话,而执行过程就是从这里开始

这里首先会判断SQL的类型:SELECT|DELETE|UPDATE|INSERT

//execute() 这里是真正执行SQL的地方public Object execute(SqlSession sqlSession, Object[] args) {//判断是哪一种SQL语句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()) {//返回Mapresult = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {//返回Cursorresult = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}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;}

得到MappedStatement对象,Executor负责执行

selectList内部调用的Executor对象执行SQL语句

 @Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {//MappedStatement:解析XML时生成的对象, 解析某一个SQL  会封装成MappedStatement,里面存放了我们所有执行SQL所需要的信息MappedStatement ms = configuration.getMappedStatement(statement);//查询,通过executorreturn 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();}}

4.Executor开始处理请求

MyBatis在查询时,不会直接查询数据库,而是会进行二级缓存的查询

在开启二级缓存的情况下,执行查询会用cachingExecutor缓存执行,如果没有数据,用SimpleExecutor去数据库查询

SQL查询(一级缓存)

 //一级缓存查询@Overridepublic <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 {//查询栈+1queryStack++;//一级缓存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 {//查询栈-1queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}//结果返回return list;}

如果一级缓存查到了,那么直接就返回结果了,如果一级缓存没有查到结果,那么最终会进入数据库进行查询。

SQL执行(数据库查询)

//数据库查询private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;//先往一级缓存中put一个占位符localCache.putObject(key, EXECUTION_PLACEHOLDER);try {//调用doQuery方法查询数据库list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}//往缓存中put真实数据localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

5.StatementHandler执行SQL语句

二级缓存查询完了之后没有数据才去真实数据库查询,这里使用statementHandler对象,要参数赋值完毕之后才执行

//真实数据库查询@Overridepublic <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也是MyBatis四大对象之一StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//#{} -> ? 的SQL在这里初始化stmt = prepareStatement(handler, ms.getStatementLog());//参数赋值完毕之后,才会真正地查询。return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}

6.ParameterHandler设置参数

在真正的数据库查询之前,要先将占位符换成真实的参数值,所以接下来会进行参数的赋值。

 //由于是#{},所以使用的是prepareStatement,预编译SQLprivate Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;//拿连接对象Connection connection = getConnection(statementLog);//初始化prepareStatementstmt = handler.prepare(connection, transaction.getTimeout());//获取了PrepareStatement之后,这里给#{}赋值handler.parameterize(stmt);return stmt;}

正式执行

当参数赋值完毕后,SQL就可以执行了

7.ResultSetHandler处理结果集

  @Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {//通过jdbc进行数据库查询。PreparedStatement ps = (PreparedStatement) statement;ps.execute();//处理结果集 resultSetHandler 也是MyBatis的四大对象之一return resultSetHandler.handleResultSets(ps);}

Mybatis源码解析——入门级相关推荐

  1. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  2. mybatis源码解析(一)

    Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...

  3. Mybatis源码解析《二》

    导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...

  4. mybatis源码解析一 xml解析(解析器)

    最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...

  5. 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖

    移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...

  6. Mybatis源码解析(一):环境搭建

    Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...

  7. mybatis源码解析1_sessionFactory

    注 一下内容都是根据尚硅谷2018年mybatis源码解析做的课程笔记,视频链接: https://www.bilibili.com/video/BV1mW411M737?p=74&share ...

  8. Mybatis 源码解析 -- 基于配置的源码解析(二)

    为什么80%的码农都做不了架构师?>>>    mapper解析 接着上篇的配置,本篇主要讲解mappers标签 <?xml version="1.0" e ...

  9. mybatis源码深度解析_30天消化MyBatis源码解析笔记,吊打面试官,offer接到手软

    MyBatis 是一个优秀的 Java 持久化框架,SSM 框架组合(Spring + SpringMVC + Mybatis),依赖 MyBatis 搭建的项目更是数不胜数,在互联网公司的使用中,占 ...

最新文章

  1. 用python画简单的四叶草-使用 python 操作 redis
  2. 初识Nginx服务器
  3. sql 取重复key中的第一条_SQL每日一题
  4. GDCM:gdcm::PixelFormat的测试程序
  5. git for windows_干货分享 | 嵌入式必备技能之Git的使用
  6. js根据数组中对象的多个属性值进行排序
  7. 转:android 避免内存泄露
  8. 【深度学习】深度学习的四大组件
  9. 利用diamond进行dbcan数据库建库并进行CAZyme注释(2022.8)
  10. uni-app 开发跨平台应用前端框架
  11. 伺服电机常用参数设置_伺服电机功能及作用_伺服电机参数设置
  12. 轻松获奖五一数学建模和蓝桥杯
  13. sentinel实现限流、降级、熔断配置和测试使用
  14. 时钟相位噪声测量中的杂散
  15. oracleTNS-12555: TNS:permission denied、TNS-12541: TNS:no listener、Instance orcl, status UNKNOWN
  16. 职场修炼圣经-和繁重的工作一起修行
  17. html复杂表格,横向多级表头,纵向多级表头,合并行或列
  18. vue中使用svg图片
  19. u盘连接计算机无法识别usb设备,u盘插电脑显示:跟这台计算机连接的前一个USB设备工作不正常,windows无法识别它。...
  20. 使用Latex进行中文排版

热门文章

  1. GPS-NMEA解析代码
  2. 做计算机实验报告的总结,制作网线实验报告与总结
  3. [导入]剿杀diskman.exe木马病毒
  4. MAC删除多余的声音驱动文件
  5. 星软员工为四川地震遇难同胞默哀
  6. 03 ,似然函数求解 :目标函数推导,对数似然求解,最小二乘法
  7. 在keil中如何切换stm32的大中小容量芯片
  8. 【AI测试】人工智能测试整体介绍——第六部分
  9. 【操作系统】实验六 系统内存使用统计
  10. 防火墙系列(二)-----防火墙的主要技术之包过滤技术,状态检测技术