Mybatis源码阅读(一):Mybatis初始化1.3 —— 解析sql片段和sql节点

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新

前言

接上一篇博客,解析核心配置文件的流程还剩两块。Mybatis初始化1.2 —— 解析别名、插件、对象工厂、反射工具箱、环境

本想着只是两个模块,随便写写就完事,没想到内容还不少,加上最近几天事情比较多,就没怎么更新,几天抽空编写剩下两块代码。
解析sql片段

sql节点配置在Mapper.xml文件中,用于配置一些常用的sql片段。

/*** 解析sql节点。* sql节点用于定义一些常用的sql片段* @param list*/
private void sqlElement(List<XNode> list) {if (configuration.getDatabaseId() != null) {sqlElement(list, configuration.getDatabaseId());}sqlElement(list, null);
}/*** 解析sql节点* @param list sql节点集合* @param requiredDatabaseId 当前配置的databaseId*/
private void sqlElement(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {// 获取databaseId和id属性String databaseId = context.getStringAttribute("databaseId");// 这里的id指定的是命名空间String id = context.getStringAttribute("id");// 启用当前的命名空间id = builderAssistant.applyCurrentNamespace(id, false);if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {// 如果该节点指定的databaseId是当前配置中的,就启用该节点的sql片段sqlFragments.put(id, context);}}
}

这里面,SQLFragments用于存放sql片段。在存放sql片段之前,会先调用databaseIdMatchesCurrent方法去校验该片段的databaseId是否为当前启用的databaseId

/*** 判断databaseId是否是当前启用的* @param id 命名空间id* @param databaseId 待匹配的databaseId* @param requiredDatabaseId 当前启用的databaseId* @return*/
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 databaseIdXNode context = this.sqlFragments.get(id);return context.getStringAttribute("databaseId") == null;
}

解析sql片段的步骤就这么简单,下面是解析sql节点的代码。
解析sql节点

在XxxMapper.xml中存在诸多的sql节点,大体分为select、insert、delete、update节点(此外还有selectKey节点等,后面会进行介绍)。每一个sql节点最终会被解析成MappedStatement。

/**

  • 表示映射文件中的sql节点

  • select、update、insert、delete节点

  • 该节点中包含了id、返回值、sql等属性

  • @author Clinton Begin
    */
    public final class MappedStatement {

    /**

    • 包含命名空间的节点id
      /
      private String resource;
      private Configuration configuration;
      /
      *
    • 节点id
      /
      private String id;
      private Integer fetchSize;
      private Integer timeout;
      /
      *
    • STATEMENT 表示简单的sql,不包含动态的
    • PREPARED 表示预编译sql,包含#{}
    • CALLABLE 调用存储过程
      */
      private StatementType statementType;
      private ResultSetType resultSetType;

    /**

    • 节点或者注解中编写的sql
      /
      private SqlSource sqlSource;
      private Cache cache;
      private ParameterMap parameterMap;
      private List resultMaps;
      private boolean flushCacheRequired;
      private boolean useCache;
      private boolean resultOrdered;
      /
      *
    • sql的类型。select、update、insert、delete
      */
      private SqlCommandType sqlCommandType;
      private KeyGenerator keyGenerator;
      private String[] keyProperties;
      private String[] keyColumns;
      private boolean hasNestedResultMaps;
      private String databaseId;
      private Log statementLog;
      private LanguageDriver lang;
      private String[] resultSets;
      }

处理sql节点

/*** 处理sql节点* 这里的Statement单词后面会经常遇到* 一个MappedStatement表示一条sql语句* @param list*/
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);
}/*** 启用当前databaseId的sql语句节点* @param list* @param requiredDatabaseId*/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {// 解析sql节点statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}
}

在parseStatementNode方法中,只会启用当前databaseId的sql节点(如果没配置就全部启用)

/*** 解析sql节点*/
public void parseStatementNode() {// 当前节点idString id = context.getStringAttribute("id");// 获取数据库idString databaseId = context.getStringAttribute("databaseId");// 启用的数据库和sql节点配置的不同if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 获取当前节点的名称String nodeName = context.getNode().getNodeName();// 获取到sql的类型。select|update|delete|insertSqlCommandType 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和selectKey节点......

}

在该方法中,会依次处理include节点、selectKey节点、最后获取到当前sql节点的各个属性,去创建MappedStatement对象,并添加到Configuration中。

/*** 解析sql节点*/
public void parseStatementNode() {// 在上面已经进行了注释......// 解析sql前先处理include节点。XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// 获取parameterType属性String parameterType = context.getStringAttribute("parameterType");// 直接拿到parameterType对应的ClassClass<?> parameterTypeClass = resolveClass(parameterType);// 获取到lang属性String lang = context.getStringAttribute("lang");// 获取对应的动态sql语言驱动器。LanguageDriver langDriver = getLanguageDriver(lang);// 解析selectKey节点processSelectKeyNodes(id, parameterTypeClass, langDriver);

}

解析parameterType和lang属性比较简单,这里只看解析include和selectKey
解析include节点

/*** 启用include节点** @param source*/
public void applyIncludes(Node source) {Properties variablesContext = new Properties();Properties configurationVariables = configuration.getVariables();Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);applyIncludes(source, variablesContext, false);
}

在applyIncludes方法中,会调用它的重载方法,递归去处理所有的include节点。include节点中,可能会存在${}占位符,在这步,也会将该占位符给替换成实际意义的字符串。接着,include节点会被处理成sql节点,并将sql节点中的sql语句取出放到节点之前,最后删除sql节点。最终select等节点会被解析成带有动态sql的节点。

/*** 递归去处理所有的include节点.** @param source           include节点* @param variablesContext 当前所有的配置*/
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {if (source.getNodeName().equals("include")) {// 获取到refid并从配置中拿到sql片段Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);// 解析include节点下的Properties节点,并替换value对应的占位符,将name和value键值对形式存放到variableContextProperties toIncludeContext = getVariablesContext(source, variablesContext);// 递归处理,在sql节点中可能会使用到include节点applyIncludes(toInclude, toIncludeContext, true);if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true);}// 将include节点替换成sql节点source.getParentNode().replaceChild(toInclude, source);while (toInclude.hasChildNodes()) {// 如果还有子节点,就添加到sql节点前面// 在上面的代码中,sql节点已经不可能再有子节点了// 这里的子节点就是文本节点(具体的sql语句)toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 删除sql节点toInclude.getParentNode().removeChild(toInclude);} else if (source.getNodeType() == Node.ELEMENT_NODE) {if (included && !variablesContext.isEmpty()) {NamedNodeMap attributes = source.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {Node attr = attributes.item(i);attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));}}// 获取所有的子节点NodeList children = source.getChildNodes();for (int i = 0; i < children.getLength(); i++) {// 解析include节点applyIncludes(children.item(i), variablesContext, included);}} else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE)&& !variablesContext.isEmpty()) {// 使用之前解析到的Properties对象替换对应的占位符source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));}
}

第一行代码的含义是根据include节点的refid属性去获取到对应的sql片段,代码比较简单

/*** 根据refid查找sql片段* @param refid* @param variables* @return*/
private Node findSqlFragment(String refid, Properties variables) {// 替换占位符refid = PropertyParser.parse(refid, variables);// 将refid前面拼接命名空间refid = builderAssistant.applyCurrentNamespace(refid, true);try {// 从Configuration中查找对应的sql片段XNode nodeToInclude = configuration.getSqlFragments().get(refid);return nodeToInclude.getNode().cloneNode(true);} catch (IllegalArgumentException e) {throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);}
}

到这里,include节点就会被替换成有实际意义的sql语句。
解析selectKey节点

当数据表中主键设计为自增,可能会存在业务需要在插入后获取到主键,这时候就需要使用selectKey节点。processSelectKeyNodes方法用于解析selectKey节点。该方法会先获取到该sql节点所有的selectKey节点,遍历去解析,解析完毕后删除selectKey节点。

/*** 解析selectKey节点* selectKey节点可以解决insert时主键自增问题* 如果需要在插入数据后获取到主键,就需要使用selectKey节点** @param id                 sql节点的id* @param parameterTypeClass 参数类型* @param langDriver         动态sql语言驱动器*/
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {// 获取全部的selectKey节点List<XNode> selectKeyNodes = context.evalNodes("selectKey");if (configuration.getDatabaseId() != null) {parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());}parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);removeSelectKeyNodes(selectKeyNodes);
}

删除selectKey节点的代码比较简单,这里就不贴了,重点看parseSelectKeyNodes方法。

该方法负责遍历获取到的所有selectKey节点,只启用当前databaseId对应的节点(这里的逻辑和sql片段那里一样,如果开发者没有配置databaseId,就全部启用)

/*** 解析selectKey节点** @param parentId             父节点id(指sql节点的id)* @param list                 所有的selectKey节点* @param parameterTypeClass   参数类型* @param langDriver           动态sql驱动* @param skRequiredDatabaseId 数据源id*/
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {// 遍历selectKey节点for (XNode nodeToHandle : list) {// 拼接id 修改为形如 findById!selectKey形式String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;// 获得当前节点的databaseId属性String databaseId = nodeToHandle.getStringAttribute("databaseId");// 只解析databaseId是当前启用databaseId的节点if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);}}
}

在for循环中,会逐个调用parseSelectKeyNode方法去解析selectKey节点。代码看似复杂其实很简单,最终selectKey节点也会被解析成MappedStatement对象

/*** 解析selectKey节点** @param id                 节点id* @param nodeToHandle       selectKey节点* @param parameterTypeClass 参数类型* @param langDriver         动态sql驱动* @param databaseId         数据库id*/
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {// 获取 resultType 属性String resultType = nodeToHandle.getStringAttribute("resultType");// 解析返回值类型Class<?> resultTypeClass = resolveClass(resultType);// 解析statementType(sql类型,简单sql、动态sql、存储过程)StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));// 获取keyProperty和keyColumn属性String keyProperty = nodeToHandle.getStringAttribute("keyProperty");String keyColumn = nodeToHandle.getStringAttribute("keyColumn");// 是在之前还是之后去获取主键boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));// 设置MappedStatement对象需要的一系列属性默认值boolean useCache = false;boolean resultOrdered = false;KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;Integer fetchSize = null;Integer timeout = null;boolean flushCache = false;String parameterMap = null;String resultMap = null;ResultSetType resultSetTypeEnum = null;// 生成sqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);// selectKey节点只能配置select语句SqlCommandType sqlCommandType = SqlCommandType.SELECT;// 用这么一大坨东西去创建MappedStatement对象并添加到Configuration中builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);// 启用当前命名空间(给id前面加上命名空间)id = builderAssistant.applyCurrentNamespace(id, false);// 从Configuration中拿到上面的MappedStatementMappedStatement keyStatement = configuration.getMappedStatement(id, false);configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

至此,selectKey节点已经被解析完毕并删除掉了,其余代码就是负责解析其他属性并将该sql节点创建为MappedStatement对象。

    KeyGenerator keyGenerator;// 拼接id。形如findById!selectKeyString keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;// 给这个id前面追加当前的命名空间keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {// 优先取配置的useGeneratorKeys。如果为空就判断当前配置是否允许jdbc自动生成主键,并且当前是insert语句// 判断如果为真就创建Jdbc3KeyGenerator,如果为假就创建NoKeyGeneratorkeyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}// 获取当前sql节点的一堆属性,去创建MappedStatement。// 这里创建的MappedStatement就代表一个sql节点// 也是后面编写mybatis拦截器时可以拦截的一处SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String resultType = context.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

结语

在看本博客时,可能会觉得比较吃力,这里建议结合代码去阅读。事实上这三篇博客的阅读和编写的过程中,对应的mybatis代码都比较容易,结合代码阅读起来并没有多大难度。最后贴一下我的码云地址(别问为什么是github,卡的一批)

mybatis源码中文注释

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/536983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

添加请求头 retrofit_RxJava 与 Retrofit 结合的最佳实践

前言RxJava和Retrofit也火了一段时间了&#xff0c;不过最近一直在学习ReactNative和Node相关的姿势&#xff0c;一直没有时间研究这些新东西&#xff0c;最近有个项目准备写&#xff0c;打算先用Android写一个Demo出来&#xff0c;却发现Android的世界发生了天翻地覆的变化&am…

Mybatis源码阅读(二):动态节点解析2.1 —— SqlSource和SqlNode

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

k8s边缘节点_边缘计算,如何啃下集群管理这块硬骨头?

导读边缘计算平台&#xff0c;旨在将边缘端靠近数据源的计算单元纳入到中心云&#xff0c;实现集中管理&#xff0c;将云服务部署其上&#xff0c;及时响应终端请求。然而&#xff0c;成千上万的边缘节点散布于各地&#xff0c;例如银行网点、车载节点等&#xff0c;节点数量甚…

Mybatis源码阅读(二):动态节点解析2.2 —— SqlSourceBuilder与三种SqlSource

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

搞懂toString()与valueOf()的区别

一、toString&#xff08;&#xff09; 作用&#xff1a;toString&#xff08;&#xff09;方法返回一个表示改对象的字符串&#xff0c;如果是对象会返回&#xff0c;toString() 返回 “[object type]”,其中type是对象类型。 二、valueOf( ) 作用&#xff1a;valueOf房啊发返…

oracle入库的速度能到多少_倒车入库别练复杂了,其实就这两点

教练总会让学员反复练倒车入库&#xff0c;但不少学员都会有这样的疑惑&#xff1a;为什么每一次倒库结果都不一样&#xff0c;倒车入库的练习重点是什么&#xff1f;倒车入库是科二的重点及难点&#xff0c;但只要掌握以下两个关键&#xff0c;顺利通过真不难&#xff1a;01方…

Mybatis源码阅读(三):结果集映射3.1 —— ResultSetBuilder与简单映射

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

kdj买卖指标公式源码_通达信指标公式源码MACD背离KDJ背离指标

N1:5;N2:10;N3:21;N4:60;牛熊:EMA(CLOSE,N4),COLORGREEN,LINETHICK3;DIFF:EMA(CLOSE,12) - EMA(CLOSE,26);DEA:EMA(DIFF,8);A1:BARSLAST(REF(CROSS(DIFF,DEA),1)); B1:REF(C,A11)>C AND REF(DIFF,A11)DRAWTEXT(IF(B1>0,1,0),L-0.1,MACD底背),COLORGREEN;RSV:(CLOSE-LLV(L…

Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

gridview获取选中行数据_Word转Excel,不想熬夜加班,那就掌握这个数据清洗方法...

私信回复关键词【福利】~获取丰富办公资源&#xff0c;助你高效办公早下班&#xff01;小伙伴们&#xff0c;大家好&#xff0c;我是专治各种疑难杂「数」的农夫~今天&#xff0c;我就为大家介绍一种高效的数据清洗方法&#xff0c;助你告别熬夜加班&#xff0c;拥抱美好的夜晚…

Mybatis源码阅读(三):结果集映射3.3 —— 主键生成策略

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

list最大容量_Java 基础(四)集合源码解析 List

List 接口前面我们学习了Iterator、Collection&#xff0c;为集合的学习打下了基础&#xff0c;现在我们来学习集合的第一大体系 List。List 是一个接口&#xff0c;定义了一组元素是有序的、可重复的集合。List 继承自 Collection&#xff0c;较之 Collection&#xff0c;List…

Mybatis源码阅读(四):核心接口4.1——StatementHandler

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Shell学习之结合正则表达式与通配符的使用(五)

Shell学习之结合正则表达式与通配符的使用 目录 通配符 正则表达式与通配符通配符通配符的使用正则表达式 正则表达式正则表达式的使用通配符 正则表达式与通配符 正则表达式用来在文件中匹配符合条件的字符串&#xff0c;正则是包含匹配。grep、awk、sed等命令可以支持正则表达…

Mybatis源码阅读(四):核心接口4.2——Executor(上)

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

接收xml参数_SpringBoot实战(二):接收xml请求

强烈推荐一个大神的人工智能的教程&#xff1a;http://www.captainbed.net/zhanghan【前言】最近在对接一个第三方系统&#xff0c;需要接收第三方系统的回调&#xff0c;而且格式为XML形式&#xff0c;之前自己一般接收的参数是Json形式&#xff0c;于是乎做个实验验证一下使用…

报错 插入更新_window如何解决mysql数据量过大导致的报错

window如何解决报错“The total number of locks exceeds the lock table size”第一大步&#xff0c;查看mysql配置信息在CMD中输入mysql -hlocalhost -uroot -p #如果设置了密码直接接在p 后面 show variables like %storage_engine%以下为结果可以看到InnoDB是MySQL的默认引…

Mybatis源码阅读(四):核心接口4.2——Executor(下)

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Mybatis源码阅读(五 ):接口层——SqlSession

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

插入公式_一个小工具,彻底帮你搞定在Markdown中插入公式的问题

在编辑Markdown文档时&#xff0c;插入公式是一个挺麻烦的活儿。需要掌握LaTex语法。我自己看完语法后&#xff0c;直接放弃&#xff0c;这绝对是反人类的语法。&#xff08;好吧&#xff0c;是我不会用...&#xff09;但是&#xff0c;我相信你看了这篇文章后&#xff0c;绝对…