第三章-爱代码爱编程
3.2.2.1 cache-ref节点解析
private void cacheRefElement(XNode context) {
if (context != null) {
// 取出该节点的属性值namespace,并存到configuration中,内部是一个HashMap集合,看如下`关联1`
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
// 创建 CacheRefResolver 对象,看如下`关联2`
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// 解析cache-ref后,从configuration中拿到namespace对应的cache,作为当前缓存使用,看如下`关联3`
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
// 解析失败,将其添加到未完成列表中,内部是LinkedList实现,就是章节`3.2.1`中Pending要清除的
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
// 关联1:存放cache-ref的配置值
public void addCacheRef(String namespace, String referencedNamespace) {
cacheRefMap.put(namespace, referencedNamespace);
}
// 关联2:构造函数中就是普通赋值
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
this.assistant = assistant;
this.cacheRefNamespace = cacheRefNamespace;
}
// 关联3
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
public Cache useCacheRef(String namespace) {
if (namespace == null) { // 判空
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
// 从 configuration中取出namespace对应的cache,内部也是HashMap集合存放
Cache cache = configuration.getCache(namespace);
if (cache == null) { // 再次判空
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
// 作为当前缓存对象
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
3.2.2.2 cache节点解析
private void cacheElement(XNode context) {
if (context != null) {
// type表示自定义cache实现类
String type = context.getStringAttribute("type", "PERPETUAL");
// 判断是不是别名,并解析
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 缓存清除策略,默认LRU
String eviction = context.getStringAttribute("eviction", "LRU");
/* 判断是不是别名,并解析 eviction 策略对应的实现类,Mybatis默认4个实现方式FifoCache、LruCache、SoftCache、WeakCache,分别对应FIFO、LRU、SOFT、WEAK策略
LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
*/
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// flushInterval(刷新间隔)
Long flushInterval = context.getLongAttribute("flushInterval");
// size(引用数目)属性可以被设置为任意正整数,要注意缓存对象的大小和运行环境中可用的内存资源。默认值是 1024
Integer size = context.getIntAttribute("size");
// readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
// 解析cache节点下的子节点property,并全部解析为属性信息
Properties props = context.getChildrenAsProperties();
// 构建缓存对象Cache
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
构建缓存对象
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// 通过命名空间作为id,来唯一标识缓存范围
Cache cache = new CacheBuilder(currentNamespace)
// 设置缓存实现类
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
// 设置缓存清除策略实现类
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
// 设置刷新间隔时间
.clearInterval(flushInterval)
// 设置缓存对象数
.size(size)
// 缓存是否读写
.readWrite(readWrite)
// 这个值用在多线程情况,用到时再讲
.blocking(blocking)
// 设置属性
.properties(props)
.build(); // 真正构建Cache的地方,继续往下
// 将创建的 cache 对象保存到 configuration 的 HashMap中,key 就是 命名空间,value 就是 cache
configuration.addCache(cache);
currentCache = cache; // 设置当前使用的缓存对象
return cache; // 返回
}
CacheBuilder构建Cache
public Cache build() {
// 如何用户没有自定义Cache实现类,就用默认的PerpetualCache
setDefaultImplementations();
// 这里就是通过反射创建 implementation 对应的类的对象
Cache cache = newBaseCacheInstance(implementation, id);
// 检验cache的setter方法,并把对应的setter方法通过属性名称,将属性值设置进去,也就是赋值
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
// 下面这一段代码就是分别区分自定义缓存实现和默认缓存实现,并通过装饰器模式,强化缓存的实现,也就是说,不管是默认的缓存实现PerpetualCache,还是自定义的,都得在外再包一层或多层Mybatis实现的缓存
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
// Mybatis自带的用于装饰的缓存实现,构建函数都需要传入一个被包装的缓存实现,所以这里要传入cache并反射创建装饰的缓存实现
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache); // 设置属性
}
// Mybatis自身通过装饰器再包一层,具体就在这实现,看`关联1`
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
// 如果自定义的类不是LoggingCache了类,那得用 LoggingCache 装饰一层
cache = new LoggingCache(cache);
}
// 返回最终的cache
return cache;
}
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
// 关联1
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
// 设置size
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
// 设置 clearInterval
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
// 可读写缓存,用SerializedCache装饰一层
if (readWrite) {
cache = new SerializedCache(cache);
}
// 再用LogginCache装饰一层
cache = new LoggingCache(cache);
// 再用 SynchronizedCache 装饰一层
cache = new SynchronizedCache(cache);
// blocking 为 true,再用 BlockingCache 装饰一层
if (blocking) {
cache = new BlockingCache(cache);
}
// 返回最外层的cache
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
3.2.2.3 resultMap节点解析
先看看 resultMap 节点中有什么,结果映射(resultMap)
- constructor - 用于在实例化类时,注入结果到构造方法中
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result
– 注入到字段或 JavaBean 属性的普通结果- association – 一个复杂类型的关联;许多结果将包装成这种类型
- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
- collection – 一个复杂类型的集合
- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
- discriminator – 使用结果值来决定使用哪个resultMap
- case – 基于某些值的结果映射
- 嵌套结果映射 –
case
也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
- case – 基于某些值的结果映射
所以,下面的代码,就是把resultMap的配置解析成 ResultMap 对象,并存放到 configuration 的 HashMap 中,由于正常生产环境的 resultMap 配置不会很复杂,但是它定义的又很复杂,解析起来,又繁琐,又枯燥,并且意义不大,所以这段代码就不一一解析了,有兴趣的读者可以自行研究。
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) { // 遍历子节点
try {
// 解析节点,继续往下看
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
3.2.2.4 sql节点解析
private void sqlElement(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 直接看这里,没有进一步的对sql节点里的sql内容进行解析,而是直接把当前的sql节点XNode对象整个存放到 sqlFragments 这个Map中
sqlFragments.put(id, context);
}
}
}
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
return requiredDatabaseId.equals(databaseId);
}
if (databaseId != null) {
return false;
}
if (!this.sqlFragments.containsKey(id)) {
return true;
}
// skip this fragment if there is a previous one with a not null databaseId
XNode context = this.sqlFragments.get(id);
return context.getStringAttribute("databaseId") == null;
}
3.2.2.5 select|insert|update|delete解析
调用入口在 XMLMapperBuilder.java 类中
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 真正解析在这里
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
真正解析操作在 XMLStatementBuilder.java 类中
public void parseStatementNode() {
String id = context.getStringAttribute("id"); // 语句的id
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 节点名,用于判断 select|insert|update|delete
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 判断是否select
// 非 select 时,flushCache 为 true,也就是说 insert|update|delete 语句的执行会刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// select 时,useCache 默认为 true,将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// include 解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取 LanguageDriver,默认实现 XMLLanguageDriver,后面会用它来解析sql并生成SqlSource对象,还有一个实现是 RawLanguageDriver,具体用到时再讲
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
// selectKey 语句的解析,这个内容也比较多,专门抽出一节来讲,看章节`3.2.2.7`
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
// SelectKeyGenerator.SELECT_KEY_SUFFIX = "!selectKey"
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
// 最终 keyStatementId = namespace + 语句id + "!selectKey"
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// keyGenerator 在上面解析selectKey的方法 processSelectKeyNodes 中生成
if (configuration.hasKeyGenerator(keyStatementId)) {
// 有就取出来
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 没有就根据 useGeneratedKeys是否为true及是否为insert语句来判断,使用 Jdbc3KeyGenerator,否则为 NoKeyGenerator
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 生成 sqlSource ,细节看章节`3.2.2.6`
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 开始 ========== 下面这些值,官网都有介绍,我就再写一遍吧 ==============//
// statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)
Integer fetchSize = context.getIntAttribute("fetchSize");
// timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
Integer timeout = context.getIntAttribute("timeout");
// parameterMap 用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
String parameterMap = context.getStringAttribute("parameterMap");
// resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
// resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
String resultMap = context.getStringAttribute("resultMap");
// resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
// keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// ========== 下面这些值,官网都有介绍,我就再写一遍吧 ============== 结束//
// 创建 MappedStatement 并存放到 configuration 的 HashMap 中,细节放到章节`3.2.2.6`中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。