mybatis3源码篇(1)——构建流程

news/2024/10/16 18:12:35/

mybatis 版本:v3.3.0

文章目录

  • 构建流程
    • SqlSessionFactoryBuilder
      • XMLConfigBuilder
        • typeAliasesElement
        • typeHandlerElement
        • mapperElement
          • MapperRegistry
      • MappedStatement
        • MapperAnnotationBuilder
        • XMLMapperBuilder
        • MapperBuilderAssistant
    • SqlSessionFactory
    • SqlSession

mybatis的源码可以从构建流程和执行流程两个角度来看。
构建流程就是解析xml配置文件,将所配置的信息全部解析到一个Configuration对象中。
执行流程就是一个sql语句的执行,其中包括SQL语句的获取,类型转化等操作,参数处理,结果集映射等操作。
在这里插入图片描述

构建流程

先看我们是如何使用mybatis的
在这里插入图片描述
可以发现,使用前要先构建一个SqlSeesion。现在针对SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession逐个分析。

SqlSessionFactoryBuilder

这里用到了构建者设计模式,build方法传入配置文件资源并通过XMLConfigBuilder解析配置文件得到一个Configuration对象,再把这个Configuration对象传递给SqlSessionFactory构造方法得到一个SqlSeesionFactory。
在这里插入图片描述

XMLConfigBuilder

而XMLConfigBuilder解析过程如下
在这里插入图片描述
解析XML的底层实现是w3c的dom技术,mybatis只是对其做了一些封装。
这里着重看一下typeAliasesElement、typeHandlerElement、mapperElement

typeAliasesElement

注册我们的自定义别名
在这里插入图片描述
同时mybatis会默认对一些数据类型注册别名,在TypeAliasRegistry的构造方法中就注册。
在这里插入图片描述
这也是为什么我们基本数据类型可以直接用string,int,long…而不用写全名的原因。

typeHandlerElement

注册我们自定义的类型处理器
在这里插入图片描述
同样,mybatis也会默认对一些数据类型注册类型处理器。
在这里插入图片描述

mapperElement

解析Mappers标签
在这里插入图片描述
if语句的4个分支分别对应包名,类路径,绝对url路径,类名配置的mapper。

MapperRegistry

针对引用包名和类(引用class)方式,mybatis调用configuration.addMapper(s),跟踪器代码发现本质上调用的是MapperRegistry.addMapper。而这个注册器通过MapperAnnotationBuilder解析Mapper。
在这里插入图片描述
而针对引用类路径,绝对url路径(引用资源)方式,mybatis直接通过XMLMapperBuilder去解析。

MapperAnnotationBuilder和XMLMapperBuilder看名字都是只解析注解或XML方式的配置,但实际上两者在解析的过程中是会相互调用的。所以无论你用何种方式,最终都会解析注解和xml配置。

MappedStatement

前面有提到,最终解析出来的配置都会设置到Configuration。所以MapperAnnotationBuilder、XMLMapperBuilder解析出来了啥呢?
其实就是一个个的MappedStatement,其属性如下。
在这里插入图片描述
每个MappedStatement对象都对应一个映射(注解或xml配置)中的select、insert、update或delete元素,它包含了执行SQL语句所需的所有信息,如SQL语句(sqlSource)、参数映射关系(parameterMap)、结果映射关系(resultMaps)等。

回到MapperAnnotationBuilder、XMLMapperBuilder,来看看这两个是怎么添加MappedStatement到Configuragtion的。

MapperAnnotationBuilder

在这里插入图片描述
parseStatement方法如下
先读取注解配置的所有信息,然后调用MapperBuilderAssistant.addMappedStatement添加MappedStatement到Configuration。

void parseStatement(Method method) {Class<?> parameterTypeClass = getParameterType(method);LanguageDriver languageDriver = getLanguageDriver(method);SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource != null) {Options options = method.getAnnotation(Options.class);final String mappedStatementId = type.getName() + "." + method.getName();Integer fetchSize = null;Integer timeout = null;StatementType statementType = StatementType.PREPARED;ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;SqlCommandType sqlCommandType = getSqlCommandType(method);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = !isSelect;boolean useCache = isSelect;KeyGenerator keyGenerator;String keyProperty = "id";String keyColumn = null;if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {// first check for SelectKey annotation - that overrides everything elseSelectKey selectKey = method.getAnnotation(SelectKey.class);if (selectKey != null) {keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);keyProperty = selectKey.keyProperty();} else if (options == null) {keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();} else {keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();keyProperty = options.keyProperty();keyColumn = options.keyColumn();}} else {keyGenerator = new NoKeyGenerator();}if (options != null) {flushCache = options.flushCache();useCache = options.useCache();fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348timeout = options.timeout() > -1 ? options.timeout() : null;statementType = options.statementType();resultSetType = options.resultSetType();}String resultMapId = null;ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);if (resultMapAnnotation != null) {String[] resultMaps = resultMapAnnotation.value();StringBuilder sb = new StringBuilder();for (String resultMap : resultMaps) {if (sb.length() > 0) {sb.append(",");}sb.append(resultMap);}resultMapId = sb.toString();} else if (isSelect) {resultMapId = parseResultMap(method);}assistant.addMappedStatement(mappedStatementId,sqlSource,statementType,sqlCommandType,fetchSize,timeout,// ParameterMapIDnull,parameterTypeClass,resultMapId,getReturnType(method),resultSetType,flushCache,useCache,// TODO issue #577false,keyGenerator,keyProperty,keyColumn,// DatabaseIDnull,languageDriver,// ResultSetsnull);}}

XMLMapperBuilder

在这里插入图片描述
来到configurationElement方法
在这里插入图片描述
来到buildStatementFromContext方法
在这里插入图片描述

XMLStatementBuilder
因为XML配置比较复杂,所以mybatis再次用到构建者的设计模式,通过XMLStatementBuilder去构建MappedStatement。
parseStatementNode方法如下

public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");//如果databaseId不匹配,退出if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}//暗示驱动程序每次批量返回的结果行数Integer fetchSize = context.getIntAttribute("fetchSize");//超时时间Integer timeout = context.getIntAttribute("timeout");//引用外部 parameterMap,已废弃String parameterMap = context.getStringAttribute("parameterMap");//参数类型String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);//引用外部的 resultMap(高级功能)String resultMap = context.getStringAttribute("resultMap");//结果类型String resultType = context.getStringAttribute("resultType");//脚本语言,mybatis3.2的新功能String lang = context.getStringAttribute("lang");//得到语言驱动LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);//结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种String resultSetType = context.getStringAttribute("resultSetType");//语句类型, STATEMENT|PREPARED|CALLABLE 的一种StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);//获取命令类型(select|insert|update|delete)String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//是否要缓存select结果boolean useCache = context.getBooleanAttribute("useCache", isSelect);//仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。//这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsing//解析之前先解析<include>SQL片段XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.//解析之前先解析<selectKey>processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)//解析成SqlSource,一般是DynamicSqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);String resultSets = context.getStringAttribute("resultSets");//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyProperty = context.getStringAttribute("keyProperty");//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}//又去调助手类builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

最终跟注解的方式一样,调用MapperBuilderAssistant.addMappedStatement添加MappedStatement到Configuration。

MapperBuilderAssistant

 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加上namespace前缀id = applyCurrentNamespace(id, false);//是否是select语句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);//1.参数映射setStatementParameterMap(parameterMap, parameterType, statementBuilder);//2.结果映射setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);MappedStatement statement = statementBuilder.build();//建造好调用configuration.addMappedStatementconfiguration.addMappedStatement(statement);return statement;}

SqlSessionFactory

SqlSessionFactory是一个接口,有两个实现,常用的是DefaultSqlSessionFactory。DefaultSqlSessionFactory中维护了一个Configuration对象,后续都会根据这个Configuration去创建SqlSession。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SqlSession

SqlSession同样也是一个接口,用到了门面设计模式,所有sql操作都通过这个门面去操作。而且也有两个实现,常用的是DefaultSqlSession,DefaultSqlSession中维护了Executor执行器,用来执行真正的sql逻辑。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


http://www.ppmy.cn/news/51174.html

相关文章

数据结构(数组、链表、栈、队列、树)

文章目录 1.数组1.1数组的特点1.2自定义数组 2.链表2.1链表的特点2.2自定义链表2.2.1自定义单向链表2.2.2自定义双向链表 3.栈3.1栈的特点3.2 Stack使用举例3.3 自定义栈 4. 队列5. 树与二叉树5.1 树的理解5.2 二叉树的基本概念5.3 二叉树的遍历5.4 经典二叉树和红黑树5.5 二叉…

SEO机制算是让我玩明白了

获取当前时间时间戳&#xff0c;返回遵循ISO 8601扩展格式的日期 new Date(Date.now()).toISOString() 使用moment库转换回来 this.moment(new Date(Date.now()).toISOString()).format("YYYY-MM-DD") js去掉富文本中html标签和图片 filterHtmlTag(val) {if(!val){…

苦中作乐 ---竞赛刷题31-40(15-20)

&#xff08;一&#xff09;目录 L1-032 Left-pad L1-033 出生年 L1-034 点赞 L1-035 情人节 L1-039 古风排版 &#xff08;二&#xff09;题目 L1-032 Left-pad 根据新浪微博上的消息&#xff0c;有一位开发者不满NPM&#xff08;Node Package Manager&#xff09;的做法…

C语言函数大全-- q 开头的函数

C语言函数大全 本篇介绍C语言函数大全-- q 开头的函数 1. qsort 1.1 函数说明 函数声明函数功能void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));用于将指定数组按指定顺序进行排序 参数&#xff1a; base &#xff1a; 指…

马斯克爆料Twitter裁了八成员工;OpenAI CEO:GPT-5根本不存在;小鹏被曝年终奖打0.5折 | AI一周资讯

来源: AI前线 微信号&#xff1a;ai-front 整理 | 凌敏 微软宣布开源 Deep Speed Chat&#xff1b;消息称软银旗下 Arm 启动赴美 IPO&#xff1b;国家网信办出台生成式 AI 管理办法&#xff1b;前理想 AI 芯片一号位骄旸加入三星&#xff0c;负责组建 GPU 团队…… 资 讯 Op…

九、Golang测试和性能

一、单元测试 单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。 在Golang中有几种方法写单元测试&#xff0c;基础测试只使用一组参数和结果来测试一段代码。 表组测试也会测试一段代码&#xff0c;但是会使用多组参数和结果进行测试。也可以使用一些方法来模仿测…

Vue3 + TS4.8踩坑之配置husky问题env: sh\r: No such file or directory

一、基本情况&#xff1a; 硬件环境&#xff1a;MacOS 10.14.6 背景&#xff1a; 1&#xff0c;用vue3官方npm init vuelatest初始化创建的vue3 ts4.8项目&#xff0c;IDE是 VS Cde 1.77.3版本 2&#xff0c;初始化项目之后给项目配置了.editorconfig&#xff0c;方便团队…

基于ansible初始化linux服务器基础环境。

大家好&#xff0c;今天我要和大家分享一个关于搭建centos环境的新方法。 以前我们经常会看到一些文章介绍如何搭建centos环境&#xff0c;但很多时候都会出现一些问题。不过现在有了一种新的方法&#xff0c;就是使用ansible脚本来实现。 虽然这种方法仅适用于centos7&#…