### 重新认识MappedStatement
每个MappedStatement对应了我们自定义Mapper接口中的一个方法,它保存了开发人员编写的SQL语句、参数结构、返回值结构、Mybatis对它的处理方式的配置等细节要素,是对一个SQL命令是什么、执行方式的完整定义。可以说,有了它Mybatis就知道如何去调度四大组件顺利的完成用户请求。
MappedStatement保存在Configuration#mappedStatements这个Map类型的对象中,其存储的key为MappedStatement#id,所以MappedStatement的id是不能重复的,这个id是由Mapper接口的完全限定名和方法名称拼接而成,这就导致了我们在同一个Mapper中不能出现重载的接口方法。
按照Mybatis的规范,每个Mapper方法也会对应xml中一个select/insert/update/delete标签,Mybatis为这些标签设计了一些属性,允许我们开发人员修改Mybatis的运行方式或行为。大部分情况下,我们不会关注这些属性,是因为Mybatis为其设计了默认值,方便我们开箱即用。我把MappedStatement类中的主要字段进行了注释,这些字段都可以找到与之对应的标签属性,可参考[《XML映射文件》](https://mybatis.org/mybatis-3/zh/sqlmap-xml.html),代码贴在了下面,大家先对MappedStatement有个大概的认识。
~~~java
public final class MappedStatement {
/**
* 对应所属mapper的资源路径,如我们示例中的CompanyMapper.xml
*/
private String resource;
/**
* mybatis全局的配置对象
*/
private Configuration configuration;
/**
* 当前MappedStatement的唯一识别ID,并且在同一个Configuration中是唯一的
* 它由Mapper类的完全限定名和Mapper方法名称拼接而成
*/
private String id;
/**
* mybatis每次从数据库中返回记录的大小,通过对该值的优化,可以提升查询效率
*/
private Integer fetchSize;
/**
* 当前MappedStatement执行时,数据库操作的超时时间
*/
private Integer timeout;
/**
* SQL声明的类型,决定当前MappedStatement由哪种类型的StatementHandler执行
* StatementType枚举有:STATEMENT, PREPARED, CALLABLE
* 默认值是:PREPARED
*/
private StatementType statementType;
/**
* 结果集处理类型,决定了结果集游标的移动方式:
* 只能向前移动、双向移动且对修改敏感、双向移动对修改不敏感。
*/
private ResultSetType resultSetType;
/**
* 存储我们定义的经过mybatis初步解析处理的sql语句,由若干sql节点构成,包含一些动态节点,如If条件语句。
* 在生成SqlSource之前,已经把<include></include>标签的内容转为了实际的文本对象
*/
private SqlSource sqlSource;
/**
* 二级缓存策略配置对象
*/
private Cache cache;
/**
* 参数映射,外部以何种形式对当前MappedStatement传参
*/
private ParameterMap parameterMap;
/**
* 结果映射列表,应该是只有一个的,不明白为啥是列表,可能是多个结果集返回时使用的。
*/
private List<ResultMap> resultMaps;
/**
* 是否要刷新缓存,将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空
* 对select命令,默认值为false,对insert、update、delete默认为true。
*/
private boolean flushCacheRequired;
/**
* 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
*/
private boolean useCache;
/**
* sql命令类型:如select、update、insert、delete等
*/
private SqlCommandType sqlCommandType;
//……
/**
* 语言驱动,如xml。
*/
private LanguageDriver lang;
/**
* 结果集类型列表
*/
private String[] resultSets;
}
复制代码
~~~
### MappedStatement是怎么来的?
还是以XML配置方式为例进行分析,简单说下源码查找的过程。Mapper对应的SQL语句定义在xml文件中,顺着源码会发现完成xml解析工作的是XMLMapperBuilder,其中对xml中“select|insert|update|delete”类型元素的解析方法为buildStatementFromContext;buildStatementFromContext使用了XMLStatementBuilder类对statement进行解析,并最终创建了MappedStatement。
所以,XMLStatementBuilder#parseStatementNode方法就是我们分析的重点。但是,在此之前需要有一点准备工作要做。由于MappedStatement最终是由MapperBuilderAssistant构建的,它其中存储了一些Mapper级别的共享信息并应用到MappedStatement中。所以,先来简单了解下它的由来:
~~~java
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
//省略部分字段和方法
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
//省略部分字段和方法
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//省略部分代码
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);
}
}
复制代码
~~~
* 代码14行:MapperBuilderAssistant由XMLMapperBuilder的私有构造方法创建,这里传入了全局Configuration对象和xml资源文件路径;
* 代码24~28行:设置builderAssistant的namespace。这个namespace就是我们在mapper xml中声明的,也就是我们Mapper接口的完全限定名,如com.raysonxin.dao.CompanyDao。
* 代码30行:开始了声明节点的解析。
好了,下面正是开始XMLStatementBuilder#parseStatementNode的分析了。**为了节省篇幅,我直接通过代码注释的方式进行说明了,部分我认为不关键或不常用的内容没有多说**。
~~~java
/**
* parseStatementNode方法是对select、insert、update、delete这四类元素进行解析,大体分为三个过程:
* 1、解析节点属性:如我们最常用的id、resultMap等;
* 2、解析节点内的sql语句:首先把sql语句中包含的<include></include>等标签转为实际的sql语句,然后执行静态或动态节点处理;
* 3、根据以上解析到的内容,使用builderAssistant创建MappedStatement,并加入Configuration中。
* <p>
* 以上过程中最关键的是第二步,它会根据实际使用的标签,把sql片段转为不同的SqlNode,以链表方式存储到SqlSource中。
*/
public void parseStatementNode() {
//获取标签的id属性,如selectById,对应Mapper接口中的方法名称
String id = context.getStringAttribute("id");
//获取databaseId属性,我们一般都没有写。
String databaseId = context.getStringAttribute("databaseId");
/**
* 这段代码虽然不起眼,但是一定要进去看一下:其内部完成了对id的再次赋值,
* 处理的方式是:id=namespace+"."+id,也就是当前Mapper的完全限定名+"."+id,
* 比如我们之前例子中的com.raysonxin.dao.CompanyDao.selectById
* 这也是Mapper接口中不能存在重载方法的根本原因。
* */
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
/**
* 下面这块代码会依次获取fetchSize、timeout、resultMap等属性,
* 需要注意的是,有些属性虽然我们没有设置,但是mybatis会设置默认值,
* 具体可以查看mybatis的官方说明。
*/
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
//默认值:XMLLanguageDriver
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
//默认值:PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//默认值:DEFAULT
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
/**
* 英文注释也说了,在sql解析前处理 include 标签,比如说,我们include了BaseColumns,
* 它会把这个include标签替换为BaseColumns内的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)
/**
* 到了关键步骤了:就是通过这句代码完成了从xml标签到SqlSource的转换,
* SqlSource是一个接口,这里返回的可能是DynamicSqlSource、也可能是RawSqlSource,
* 取决于xml标签中是否包含动态元素,比如 <if test=""></if>
* */
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
//下面这些是针对selectKey、KeyGenerator等进行处理,暂时跳过了。
String keyProperty = context.getStringAttribute("keyProperty");
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
/**
* 节点及属性都解析完成了,使用builderAssistant创建MappedStatement,
* 并保存到Configuration#mappedStatements。
* */
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
复制代码
~~~
我们在xml中定义的select等语句就是通过这个parseStatementNode方法解析为MappedStatement的,整体来看比较容易理解,核心就是SqlSource的创建过程,大家可以写个简单的例子一步一步调试看下。
### SqlSource是什么,如何创建的?
SqlSource是整个MappedStatement的核心,MappedStatement其他一大堆字段都是为了准确的执行它而定义的。SqlSource是个半成品的sql语句,因为对于其中的动态标签还没静态化,其中的参数也未赋值。正是如此,才为我们后续的调用执行提供了基础,接下来重点看看SqlSource的构建过程。为了先从整体上了解,我画了一个时序图来描述SqlSource的解析、创建过程。
![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/89dccb3ac8cb484cbd1d63943028e531~tplv-k3u1fbpfcp-zoom-1.image)
这个过程涉及三个参与者XMLStatementBuilder、XMLLanguageDriver、XMLScriptBuilder,核心在于XMLScriptBuilder对标签的识别与解析,我们重点看XMLScriptBuilder#parseScriptNode这个方法,如下:
~~~java
/**
* parseScriptNode字面意思,解析sql脚本节点。
* */
public SqlSource parseScriptNode() {
// parseDynamicTags处理节点中的动态标签,其实动态、静态标签都会读取,
// 只是对于动态标签会使用对应的动态标签处理器解析。
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
// 如果是动态的,创建DynamicSqlSource;否则创建RawSqlSource。
// isDynamic默认是false,parseDynamicTags处理中,只要存在动态元素,他就被置为true
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
复制代码
~~~
刚才一直再说SqlNode,那SqlNode到底是什么呢?结合一个例子,我们先简单认识一下它,同样放一张类图(图中并没有放全)来了解其家族。
![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b20e5d85a2274105bc7f649c6cc68a7f~tplv-k3u1fbpfcp-zoom-1.image)
~~~xml
<select id="selectById" resultMap="baseResultMap" >
select
<include refid="BaseColumns"></include>
from company
<if test="id != null">
where id= #{id}
</if>
</select>
复制代码
~~~
对于这个示例中的select标签的sql语句(只看2-7行),mybatis会按照标签完整性(闭合)解析为多个节点,同时根据节点中出现的元素类型创建不同类型的节点(依据是《Document Object Model (DOM) Level 3 Core Specification》中定义的12中类型),比如例子中,纯文本解析为TextSqlNode,if标签解析为IfSqlNode。
但是,我们在编写sql语句时大多数情况是多种类型混合的,所以就有了MixedSqlNode,它以List存储了所有的节点。parseDynamicTags方法的作用就是解析sql语句中的节点类型,并最终生成MixedSqlNode。
~~~java
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
NodeList children = node.getNode().getChildNodes();
//遍历节点
for (int i = 0; i < children.getLength(); i++) {
//获取当前节点
XNode child = node.newXNode(children.item(i));
//判断节点类型是否为CDATA_SECTION_NODE或TEXT_NODE
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
//获取节点的sql语句内容
String data = child.getStringBody("");
//创建TextSqlNode
TextSqlNode textSqlNode = new TextSqlNode(data);
//判断是否为动态节点,实际判断是否包含${}占位符,有就是true
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
//设置为动态sql
isDynamic = true;
} else {
//不是动态节点,创建StaticTextSqlNode
contents.add(new StaticTextSqlNode(data));
}
}
//节点类型是否为ELEMENT_NODE
else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//获取节点的名称,比如if
String nodeName = child.getNode().getNodeName();
//获取对应的处理器,如IfHandler
XMLScriptBuilder.NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//调用处理器,解析动态节点内容,这里面也会递归调用parseDynamicTags,逐层处理。
handler.handleNode(child, contents);
//设置为动态sql
isDynamic = true;
}
}
//创建MixedSqlNode
return new MixedSqlNode(contents);
}
复制代码
~~~
总结一下parseDynamicTags方法的处理过程,parseDynamicTags仅处理类型为CDATA\_SECTION\_NODE、TEXT\_NODE、ELEMENT\_NODE的节点。
* 前两种类型会首先作为TextSqlNode,若节点中没有占位符`${}`,则转为StaticTextSqlNode;若节点中有占位符`${}`,节点类型为TextSqlNode不变,但是会将这条sql设置为dynamic。这会导致后续的参数设置方式不同,引出`#{}`、`${}`的差别,我们在下一节在说明。
* 对ELEMENT\_NODE节点,会根据节点名称(if、choose等)找到对应的处理器解析为动态节点,处理器有IfHandler、WhereHandler等9种。
所以,回到XMLScriptBuilder#parseScriptNode方法,根据isDynamic的值,会创建不同类型的SqlSource。到目前为止,我们知道创建DynamicSqlSource有两种情况:一是sql节点包含if、choose、where这里动态标签时;二是使用了`${}`占位符时。其余情况会创建RawSqlSource。
好了,SqlSource的创建过程我们就分析完了,在翻上去看看那张时序图加深一下印象吧。
SqlSource创建完成后,就剩下builderAssistant#addMappedStatement这个过程了,比较简单,大家可以自己查看源码了解一下,我就不废话了。
### 本文总结
正如文章开头所说,本文的主要目的是打基础,弄清楚是什么,为什么,为以后的怎么做做好铺垫。本文分析了MappedStatement主要字段及作用、Mybatis如何创建MappedStatement、MappedStatement中的SqlSource是什么及创建过程几个问题,虽然写的很啰嗦,但是问题应该是描述的差不多了,希望能对大家的理解有一些帮助。
作者:码路印记
链接:https://juejin.cn/post/6886805132936216583
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 一.JVM
- 1.1 java代码是怎么运行的
- 1.2 JVM的内存区域
- 1.3 JVM运行时内存
- 1.4 JVM内存分配策略
- 1.5 JVM类加载机制与对象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面试相关文章
- 2.1 可能是把Java内存区域讲得最清楚的一篇文章
- 2.0 GC调优参数
- 2.1GC排查系列
- 2.2 内存泄漏和内存溢出
- 2.2.3 深入理解JVM-hotspot虚拟机对象探秘
- 1.10 并发的可达性分析相关问题
- 二.Java集合架构
- 1.ArrayList深入源码分析
- 2.Vector深入源码分析
- 3.LinkedList深入源码分析
- 4.HashMap深入源码分析
- 5.ConcurrentHashMap深入源码分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的设计模式
- 8.集合架构之面试指南
- 9.TreeSet和TreeMap
- 三.Java基础
- 1.基础概念
- 1.1 Java程序初始化的顺序是怎么样的
- 1.2 Java和C++的区别
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字节与字符的区别以及访问修饰符
- 1.7 深拷贝与浅拷贝
- 1.8 字符串常量池
- 2.面向对象
- 3.关键字
- 4.基本数据类型与运算
- 5.字符串与数组
- 6.异常处理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 数据流(Stream)
- 8.3 Java 8 并发教程:线程和执行器
- 8.4 Java 8 并发教程:同步和锁
- 8.5 Java 8 并发教程:原子变量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、数值、算术和文件
- 8.7 在 Java 8 中避免 Null 检查
- 8.8 使用 Intellij IDEA 解决 Java 8 的数据流问题
- 四.Java 并发编程
- 1.线程的实现/创建
- 2.线程生命周期/状态转换
- 3.线程池
- 4.线程中的协作、中断
- 5.Java锁
- 5.1 乐观锁、悲观锁和自旋锁
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平锁和非公平锁
- 5.3.1 说说ReentrantLock的实现原理,以及ReentrantLock的核心源码是如何实现的?
- 5.5 锁优化和升级
- 6.多线程的上下文切换
- 7.死锁的产生和解决
- 8.J.U.C(java.util.concurrent)
- 0.简化版(快速复习用)
- 9.锁优化
- 10.Java 内存模型(JMM)
- 11.ThreadLocal详解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的实现原理
- 1.DelayQueue的实现原理
- 14.Thread.join()实现原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的实际使用场景
- 五.Java I/O NIO
- 1.I/O模型简述
- 2.Java NIO之缓冲区
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之选择器
- 6.基于 Java NIO 实现简单的 HTTP 服务器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面试题
- 六.Java设计模式
- 1.单例模式
- 2.策略模式
- 3.模板方法
- 4.适配器模式
- 5.简单工厂
- 6.门面模式
- 7.代理模式
- 七.数据结构和算法
- 1.什么是红黑树
- 2.二叉树
- 2.1 二叉树的前序、中序、后序遍历
- 3.排序算法汇总
- 4.java实现链表及链表的重用操作
- 4.1算法题-链表反转
- 5.图的概述
- 6.常见的几道字符串算法题
- 7.几道常见的链表算法题
- 8.leetcode常见算法题1
- 9.LRU缓存策略
- 10.二进制及位运算
- 10.1.二进制和十进制转换
- 10.2.位运算
- 11.常见链表算法题
- 12.算法好文推荐
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事务管理
- 4.SpringMVC 运行流程和手动实现
- 0.Spring 核心技术
- 5.spring如何解决循环依赖问题
- 6.springboot自动装配原理
- 7.Spring中的循环依赖解决机制中,为什么要三级缓存,用二级缓存不够吗
- 8.beanFactory和factoryBean有什么区别
- 九.数据库
- 1.mybatis
- 1.1 MyBatis-# 与 $ 区别以及 sql 预编译
- Mybatis系列1-Configuration
- Mybatis系列2-SQL执行过程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-参数设置揭秘(ParameterHandler)
- Mybatis系列8-缓存机制
- 2.浅谈聚簇索引和非聚簇索引的区别
- 3.mysql 证明为什么用limit时,offset很大会影响性能
- 4.MySQL中的索引
- 5.数据库索引2
- 6.面试题收集
- 7.MySQL行锁、表锁、间隙锁详解
- 8.数据库MVCC详解
- 9.一条SQL查询语句是如何执行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能优化神器 Explain 使用分析
- 12.mysql中,一条update语句执行的过程是怎么样的?期间用到了mysql的哪些log,分别有什么作用
- 十.Redis
- 0.快速复习回顾Redis
- 1.通俗易懂的Redis数据结构基础教程
- 2.分布式锁(一)
- 3.分布式锁(二)
- 4.延时队列
- 5.位图Bitmaps
- 6.Bitmaps(位图)的使用
- 7.Scan
- 8.redis缓存雪崩、缓存击穿、缓存穿透
- 9.Redis为什么是单线程、及高并发快的3大原因详解
- 10.布隆过滤器你值得拥有的开发利器
- 11.Redis哨兵、复制、集群的设计原理与区别
- 12.redis的IO多路复用
- 13.相关redis面试题
- 14.redis集群
- 十一.中间件
- 1.RabbitMQ
- 1.1 RabbitMQ实战,hello world
- 1.2 RabbitMQ 实战,工作队列
- 1.3 RabbitMQ 实战, 发布订阅
- 1.4 RabbitMQ 实战,路由
- 1.5 RabbitMQ 实战,主题
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 实战 – 整合 RabbitMQ 发送邮件
- 1.8 RabbitMQ 的消息持久化与 Spring AMQP 的实现剖析
- 1.9 RabbitMQ必备核心知识
- 2.RocketMQ 的几个简单问题与答案
- 2.Kafka
- 2.1 kafka 基础概念和术语
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志机制
- 2.4 kafka是pull还是push的方式传递消息的?
- 2.5 Kafka的数据处理流程
- 2.6 Kafka的脑裂预防和处理机制
- 2.7 Kafka中partition副本的Leader选举机制
- 2.8 如果Leader挂了的时候,follower没来得及同步,是否会出现数据不一致
- 2.9 kafka的partition副本是否会出现脑裂情况
- 十二.Zookeeper
- 0.什么是Zookeeper(漫画)
- 1.使用docker安装Zookeeper伪集群
- 3.ZooKeeper-Plus
- 4.zk实现分布式锁
- 5.ZooKeeper之Watcher机制
- 6.Zookeeper之选举及数据一致性
- 十三.计算机网络
- 1.进制转换:二进制、八进制、十六进制、十进制之间的转换
- 2.位运算
- 3.计算机网络面试题汇总1
- 十四.Docker
- 100.面试题收集合集
- 1.美团面试常见问题总结
- 2.b站部分面试题
- 3.比心面试题
- 4.腾讯面试题
- 5.哈罗部分面试
- 6.笔记
- 十五.Storm
- 1.Storm和流处理简介
- 2.Storm 核心概念详解
- 3.Storm 单机版本环境搭建
- 4.Storm 集群环境搭建
- 5.Storm 编程模型详解
- 6.Storm 项目三种打包方式对比分析
- 7.Storm 集成 Redis 详解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初识ElasticSearch
- 2.文档基本CRUD、集群健康检查
- 3.shard&replica
- 4.document核心元数据解析及ES的并发控制
- 5.document的批量操作及数据路由原理
- 6.倒排索引
- 十七.分布式相关
- 1.分布式事务解决方案一网打尽
- 2.关于xxx怎么保证高可用的问题
- 3.一致性hash原理与实现
- 4.微服务注册中心 Nacos 比 Eureka的优势
- 5.Raft 协议算法
- 6.为什么微服务架构中需要网关
- 0.CAP与BASE理论
- 十八.Dubbo
- 1.快速掌握Dubbo常规应用
- 2.Dubbo应用进阶
- 3.Dubbo调用模块详解
- 4.Dubbo调用模块源码分析
- 6.Dubbo协议模块