每天用mybatis,但是mybatis的工作原理你真的知道吗?

Java架构师追风 Java架构师追风     2022-12-23     632

关键词:

近来想写一个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 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服务的
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 <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) try //1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); //2. 将查询任务委托给MyBatis 的执行器 Executor List<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 #601 if (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结果集
总结
以上三个步骤所有流程大体可以用一张图来总结
技术图片
最后
欢迎大家关注我的公众号【程序员追风】,文章都会在里面更新,整理的资料也会放在里面。
 

mybatis工作原理?

...过很多复杂的util优化的事情,提前有其他程序员做了。Mybatis是一个映射封装,他与你用util的区别就是,他将在代码块中的sql存在统一的xml文件也就是sqlmaper中。同时他将你执行sql的传参也就是执行变量进行了通配,然后映射到... 查看详情

mybatis工作原理?

...过很多复杂的util优化的事情,提前有其他程序员做了。Mybatis是一个映射封装,他与你用util的区别就是,他将在代码块中的sql存在统一的xml文件也就是sqlmaper中。同时他将你执行sql的传参也就是执行变量进行了通配,然后映射到... 查看详情

mybatis工作原理(含部分源码)

MyBatis的初始化1、读取配置文件,形成InputStreamStringresource="mybatis.xml";//加载mybatis的配置文件(它也加载关联的映射文件)InputStreaminputStream=null;try{inputStream=Resources.getResourceAsStream(resource);}catch(IOExceptione){e.pr 查看详情

mybatis的工作原理解析(代码片段)

文章目录前言一、mybatis工作原理1.1流程图1.2步骤解析1.3代码实现前言本文记录Mybatis的工作原理,做到知识梳理总结的作用。一、mybatis工作原理Mybatis的总体工作原理流程图如下图所示1.1流程图1.2步骤解析Mybatis框架在工作时... 查看详情

mybatis-8.mybatis工作原理

查看详情

hibernate和mybatis的工作原理以及区别

一、Mybatis的工作流程图(1)、原理详见: MyBatis应用程序根据XML配置文件创建SqlSessionFactory,SqlSessionFactory在根据配置,配置来源于两个地方,一处是配置文件,一处是Java代码的注解,获取一个SqlSession。SqlSession包含了执行sql所... 查看详情

mybatis核心源码深度剖析工作机制和实现原理

目录1MyBatis源码分析导入1.1为什么要看MyBatis框架的源码1.2如何深入学习MyBatis源码1.3源码分析的5大原则2MyBatis架构体系深入剖析2.1MyBatis的整体架构体系2.2MyBatis的工作机制和实现原理2.2.1接口层2.2.1.1获取SqlSession流程分析2.2.1.2SqlSess... 查看详情

浅谈mybatis连接原理

...面试的人(菜面菜),都说用的SSM框架,但是我问了一下,mybatis是怎么连接上mysql的,基本上都会说:配置好的,直接用了,今天我来抛砖引玉一下,欢迎拍砖!    什么是JDBC?    Java语言访问数据库的... 查看详情

mybatis的体系结构与核心工作原理分析

查看详情

mybatis的体系结构与核心工作原理分析

查看详情

《深入理解mybatis原理7》mybatis的二级缓存的设计原理

《深入理解mybatis原理》MyBatis的二级缓存的设计原理MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。本文将全面分析MyBatis的二级缓存的设计原理。1.MyBatis的缓存机制整体设计以及二... 查看详情

mybatis面试题:简述mybatis工作原理

1.首先要建立一个sqlSessionFactory:    建一个工具类,在里面引入核心配置文件    将核心配置文件转化成流文件    利用sqlSessionFactoryBuiler这个类调用build方法将材料(核心配置文件流)实例化一个sqlSessionfactor(sql... 查看详情

mybatis运行原理(代码片段)

...我们通过springboot快速构建调度平台,持久化框架采用Mybatis。这也是初次使用Mybatis,当然工作中只是构建一些Dao、Mapper,使用一些增删改查完成调度任务的历史记录、任务间的Dependency的记录。但是Mybatis的基本运行原... 查看详情

mybatis框架原理3:缓存

上一篇[MyBatis框架原理2:SqlSession运行过程][1]介绍了MyBatis的工作流程,其中涉及到了MyBatis缓存的使用,首先回顾一下工作流程图:如果开启了二级缓存,数据查询执行过程就是首先从二级缓存中查询,如果未命中则从一级缓存... 查看详情

mybatis之插件原理

鲁春利的工作笔记,好记性不如烂笔头转载自:深入浅出Mybatis-插件原理Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis... 查看详情

mybatis的架构原理解读

    MyBatis是Java生态中非常著名的一款ORM框架,目前在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。   理解MyBatis原理,阅读MyBatis核心源码,需要先 Mybatis的整体架构和工作原理总体思路。1、My... 查看详情

mybatis的工作原理和核心流程(代码片段)

Mybatis四大核心对象:1.SqlSession对象,包含执行Sql语句的所有方法,类似JDBCConnection.2.Executor接口,将SqlSession传递的参数动态地生成需要执行的sql语句,同时负责查询缓存的维护。类似于JDBC的Statement/PrepareStatemen... 查看详情

一文搞懂mybatis架构与工作原理(代码片段)

前言本文将从宏观角度分析Mybatis的架构与工作原理。架构Mybatis的功能架构分为三层:API接口层:提供给外部使用的接口API,开发人员通过这些API操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体... 查看详情