mybatis源码阅读(三):mybatis初始化(下)mapper解析

转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析

MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

对每个标签的属性以及作用,这里不做解释, 可以参考官方文档:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html

上一篇文章介绍了mybatis配置文件解析mappers节点的源码中有如下语句,从这里得到mapper映射文件时通过XMLMapperBuilder解析的。

一、XMLMapperBuilder

//mapper映射文件都是通过XMLMapperBuilder解析XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();
//解析mapper文件
public void parse() {// 判断是否已经加载过改映射文件if (!configuration.isResourceLoaded(resource)) {// 处理mapper节点configurationElement(parser.evalNode("/mapper"));// 将resource添加到configuration的loadedResources集合中保存 它是HashSet<String>configuration.addLoadedResource(resource);//注册mapper接口bindMapperForNamespace();}// 处理解析失败的resultMap节点parsePendingResultMaps();// 处理解析失败的cache-ref节点parsePendingCacheRefs();// 处理解析失败的sql节点parsePendingStatements();
}
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);// 解析cache-ref节点cacheRefElement(context.evalNode("cache-ref"));// 解析cache节点cacheElement(context.evalNode("cache"));// 解析parameterMap节点,这个已经被废弃,不推荐使用parameterMapElement(context.evalNodes("/mapper/parameterMap"));// 解析resultMap节点resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析sql节点sqlElement(context.evalNodes("/mapper/sql"));// 解析statement 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);}
}

1.cache节点

        它通过调用CacheBuilder的相应方法完成cache的创建。每个cache内部都有一个唯一的ID,这个id的值就是namespace。创建好的cache对象存入configuration的cache缓存中(该缓存以cache的ID属性即namespace为key,这里再次体现了mybatis的namespace的强大用处)。

/*** cache- 配置本定命名空间的缓存。*         type- cache实现类,默认为PERPETUAL,可以使用自定义的cache实现类(别名或完整类名皆可)*         eviction- 回收算法,默认为LRU,可选的算法有:*             LRU– 最近最少使用的:移除最长时间不被使用的对象。*             FIFO– 先进先出:按对象进入缓存的顺序来移除它们。*             SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。*             WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。*         flushInterval- 刷新间隔,默认为1个小时,单位毫秒*         size- 缓存大小,默认大小1024,单位为引用数*         readOnly- 只读* @param context* @throws Exception*/
private void cacheElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);Long flushInterval = context.getLongAttribute("flushInterval");Integer size = context.getIntAttribute("size");boolean readWrite = !context.getBooleanAttribute("readOnly", false);boolean blocking = context.getBooleanAttribute("blocking", false);Properties props = context.getChildrenAsProperties();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) {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();configuration.addCache(cache);currentCache = cache;return cache;
}

2.cache-ref节点

        cacheRefElement方法负责解析cache-ref元素,它通过调用CacheRefResolver的相应方法完成cache的引用。创建好的cache-ref引用关系存入configuration的cacheRefMap缓存中。

/***  cache-ref–从其他命名空间引用缓存配置。*         如果你不想定义自己的cache,可以使用cache-ref引用别的cache。*         因为每个cache都以namespace为id,*         所以cache-ref只需要配置一个namespace属性就可以了。*         需要注意的是,如果cache-ref和cache都配置了,以cache为准。* @param context*/
private void cacheRefElement(XNode context) {if (context != null) {configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));try {cacheRefResolver.resolveCacheRef();} catch (IncompleteElementException e) {configuration.addIncompleteCacheRef(cacheRefResolver);}}
}

3.resultMap节点

        resultMapElement方法负责解析resultMap元素,它通过调用ResultMapResolver的相应方法完成resultMap的解析。resultMap节点下除了discriminator子节点的其他子节点都会解析成对应的ResultMapping对象,而每个<resultMap>节点都会被解析成一个ResultMap对象,创建好的resultMap存入configuration的resultMaps缓存中(该缓存以namespace+resultMap的id为key,这里再次体现了mybatis的namespace的强大用处)。

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.<ResultMapping> emptyList());
}private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());String type = resultMapNode.getStringAttribute("type",resultMapNode.getStringAttribute("ofType",resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));String extend = resultMapNode.getStringAttribute("extends");Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");Class<?> typeClass = resolveClass(type);Discriminator discriminator = null;List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();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<ResultFlag>();if ("id".equals(resultChild.getName())) {flags.add(ResultFlag.ID);}resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));}}ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);try {return resultMapResolver.resolve();} catch (IncompleteElementException  e) {configuration.addIncompleteResultMap(resultMapResolver);throw e;}
}

4.sql节点解析

sql节点用来定义可重用的sql语句片段, sqlElement方法负责解析sql元素。id属性用于区分不同的sql元素,在同一个mapper配置文件中可以配置多个sql元素。

private void sqlElement(List<XNode> list) throws Exception {if (configuration.getDatabaseId() != null) {sqlElement(list, configuration.getDatabaseId());}sqlElement(list, null);
}private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {for (XNode context : list) {String databaseId = context.getStringAttribute("databaseId");String id = context.getStringAttribute("id");id = builderAssistant.applyCurrentNamespace(id, false);if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {// 记录到sqlFragments中保存,其实 构造函数中可以看到该字段指向了configuration的sqlFragments集合中sqlFragments.put(id, context);}}
}private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {if (requiredDatabaseId != null) {if (!requiredDatabaseId.equals(databaseId)) {return false;}} else {if (databaseId != null) {return false;}// skip this fragment if there is a previous one with a not null databaseIdif (this.sqlFragments.containsKey(id)) {XNode context = this.sqlFragments.get(id);if (context.getStringAttribute("databaseId") != null) {return false;}}}return true;
}

二、XMLStatementBuilder

映射配置文件中还有一类比较重要的节点需要解析,其实就是select|insert|update|delete 节点,这些节点主要用于定义SQL语句,他们不在由XMLMapperBuilder进行解析,而是由XMLStatementBuilder负责进行解析,每个节点会被解析成MappedStatement对象并存入到configuration对象中去。在这个方法内有几个重要的步骤,理解他们对正确的配置statement元素很有帮助。

1.MappedStatement

MappedStatement包含了这些节点的很多属性,其中比较重要的如下:

private String resource;//节点中的id 包括命名空间
private SqlSource sqlSource;//SqlSource对象,对应一条SQL语句
private SqlCommandType sqlCommandType;//SQL的类型,insert,delete,select,update

解析过程代码如下:

public void parseStatementNode() {// 获取sql节点的id以及databaseId如果和当前不匹配不加载改节点,// 如果存在id相同且databaseId不为空的节点也不在加载改节点String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 获取节点的多种属性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");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);String resultSetType = context.getStringAttribute("resultSetType");StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);// 根据节点的名称设置sqlCommandType的类型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);// 在解析SQL语句之前先处理include节点XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// 处理selectKey节点processSelectKeyNodes(id, parameterTypeClass, langDriver);// 完成节点的解析 该部分是核心SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// 获取resultSets keyProperty keyColumn三个属性String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;// 获取selectKey节点对应的selectKeyGenerator的idString 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;}// 通过MapperBuilderAssistant创建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);
}

2、解析include节点

在解析statement节点之前首先通过XMLIncludeTransformer解析include节点改过程会将include节点替换<sql>节点中定义的sql片段,并将其中的${xx}占位符换成真实的参数,

private void applyIncludes(Node source, final Properties variablesContext, boolean included) {if (source.getNodeName().equals("include")) {  // ---(2)处理include节点// 查找refid属性指向的<sql>,返回的是深克隆的Node对象Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);Properties toIncludeContext = getVariablesContext(source, variablesContext);//递归处理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>节点的前面toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 替换后删除<sql>节点toInclude.getParentNode().removeChild(toInclude);} else if (source.getNodeType() == Node.ELEMENT_NODE) {// ---(1)if (included && !variablesContext.isEmpty()) {// replace variables in attribute valuesNamedNodeMap attributes = source.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {// 遍历当前sql的子节点Node attr = attributes.item(i);attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));}}NodeList children = source.getChildNodes();for (int i = 0; i < children.getLength(); i++) {applyIncludes(children.item(i), variablesContext, included);}} else if (included && source.getNodeType() == Node.TEXT_NODE&& !variablesContext.isEmpty()) {// ---(3)// replace variables in text node 替换对应的占位符source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));}
}

3、解析selectKey节点

在insert,update节点中可以定义selectKey节点来解决主键自增问题。

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {String resultType = nodeToHandle.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));String keyProperty = nodeToHandle.getStringAttribute("keyProperty");String keyColumn = nodeToHandle.getStringAttribute("keyColumn");boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));//defaultsboolean 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的mappedStatements集合中保存builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);id = builderAssistant.applyCurrentNamespace(id, false);MappedStatement keyStatement = configuration.getMappedStatement(id, false);// 创建对应的KeyGenerator(主键自增策略),添加到configuration中configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

三、绑定Mapper接口

每个映射配置文件的命名空间可以绑定一个Mapper接口,并注册到MapperRegistry中。

// 绑定mapper接口
private void bindMapperForNamespace() {//获取映射文件的命名空间String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 解析命名空间对应的类型 即daoboundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!configuration.hasMapper(boundType)) {// 是否已经加载了// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResource//注册configuration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}}
}

 

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

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

相关文章

改造独立部署(SCD)模式下.NET Core应用程序 dotnet的exe文件启动过程

设置一个小目标 改造前 改造后 独立部署SCD模式&#xff0c;是指在使用dotnet publish 命令时带上-r 参数运行时标识符&#xff08;RID&#xff09;。 目标提出原因&#xff1a;SCD模式下文件太乱了&#xff0c;很多文件在开发时大多又涉及不到&#xff0c;发布后如果能把文件…

P1268-树的重量【图论】

正题 题目大意 一棵树有nnn个叶子节点&#xff0c;给出每两个叶子节点之间的距离。求这棵树的边权之和。 解题思路 我们考虑每次加入一个节点。两个节点时不用说。 加入第三个节点时&#xff0c;肯定是加入在节点1和节点2之间。 之后我们开始推导&#xff1a;当我们加入节点…

mybatis源码阅读(四):mapper(dao)实例化

转载自 mybatis源码阅读(四)&#xff1a;mapper(dao)实例化 在开始分析之前&#xff0c;先来了解一下这个模块中的核心组件之间的关系&#xff0c;如图&#xff1a; 1.MapperRegistry&MapperProxyFactory MapperRegistry是Mapper接口及其对应的代理对象工程的注册中心&…

ajax的封装使用

面试的时候有人问到我ajax的使用&#xff0c;当时回答的不算好&#xff0c;这里想重新总结下&#xff1a; 1、如何将配置等信息传到ajax函数里面去 这个采用的是在参数里加一个对象&#xff0c;对象里面放入字段&#xff0c;然后在ajax里设置一个option&#xff0c;通过option…

P3385-[模板]负环【SPFA】

正题 题目大意 求点1可不可以走到负环。 解题思路 用cnticnt_icnti​表示到iii的最短路经过了多少个点&#xff0c;然后求若cnti≥ncnt_i\geq ncnti​≥n且这条路是负数那么就有负环。 codecodecode #include<cstdio> #include<queue> #include<cstring> …

自定义路由匹配和生成

前言 前两篇文章主要总结了CMS系统两个技术点在ASP.NET Core中的应用&#xff1a; 《ASP.NET Core 中的SEO优化&#xff08;1&#xff09;&#xff1a;中间件实现服务端静态化缓存》 《ASP.NET Core 中的SEO优化&#xff08;2&#xff09;&#xff1a;中间件中渲染Razor视图》…

mybatis多个参数(不使用@param注解情况下),sql参数占位符正确写法

转载自 mybatis多个参数(不使用param注解情况下)&#xff0c;sql参数占位符正确写法 useActualParamName配置 useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性&#xff0c;你的工程必须采用Java 8编译&#xff0c;并且加上-parameters选项。&…

如何封装并发布一个属于自己的ui组件库

以前就一直有个想法自己能不能封装一个类似于elementui一样的组件库&#xff0c;然后发布到npm上去&#xff0c;毕竟前端说白了&#xff0c;将组件v上去&#xff0c;然后进行数据交互。借助这次端午&#xff0c;终于有机会&#xff0c;尝试自己去封装发布组件库了 我这里了只做…

P1768-天路【负环,SPFA,01分数规划,二分答案】

正题 题目链接:https://www.luogu.org/problemnew/show/P1768 题目大意 求一条回路使得路上∑vi∑pi\frac{\sum v_i}{\sum p_i}∑pi​∑vi​​最大。 解题思路 考虑01分数规划 ∑vi∑pians\frac{\sum v_i}{\sum p_i}ans∑pi​∑vi​​ans ∑vians∗∑pi\sum v_ians*\sum p_i…

听云支持.NET Core的应用性能监控

随着微软于2017年8月正式发布.NET Core 2.0&#xff0c; .NET Core 社区开始活跃&#xff0c;众多.NET开发者开始向跨平台转变。 听云于2017年11月推出了.NET Core应用监控工具&#xff0c;和听云其他语言的监控工具一样&#xff0c;.NET Core应用监控工具具有以下特征&#xf…

mybatis源码阅读(五) ---执行器Executor

转载自 mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_RESULT_HANDLER null;// 执行update&#xff0c;delete&#xff0c;insert三种类型的sql语句int update(MappedStatement ms, Object parameter…

.sync的一个用法

面试时&#xff0c;有人问了我修饰符是什么&#xff0c;就是一个点后面加一个单词&#xff0c;我当时还以为是什么文件夹后缀呢。很是尴尬 这里主要学习下.sync的一个用法 假设下场景&#xff1a; 这里有一个父组件&#xff0c;父组件中有个money&#xff0c;需要传到子组件中…

nssl1296-猫咪的进化【dp】

正题 题目大意 nnn次&#xff0c;每次有3种选择&#xff1a; 休息获得viv_ivi​点价值获得vi2v_i^2vi2​点价值且下一回合要休息 解题思路 定义fi,0/1/2f_{i,0/1/2}fi,0/1/2​表示第iii次为休息/叫一声/叫两声时的最大价值。 fi,0f_{i,0}fi,0​可以由前面任何状态转移过来。 …

[52ABP实战系列] .NET CORE实战入门第三章更新了

早安 各位道友好&#xff0c;.NET CORE入门视频的第三章也算录制完毕了。欢迎大家上传课网进行学习。 更新速度 大家也知道最近的社会新闻比较多。频繁发生404、关键字打不出来&#xff0c;我个人也在关注这些事件。导致精力分散&#xff0c;没有做到稳定更新&#xff0c;现在呢…

如何安装nuxt

因为vue是单页面应用&#xff0c;所以不被Seo&#xff0c;如百度和Google抓取到&#xff0c;在Vue中如果想要爬虫爬到就必须使用nuxt 那么如何安装使用呢&#xff1f; yarn create nuxt-app <project-name> cd <project-name> yarn build yarn start必须先build&a…

mybatis源码阅读(六) ---StatementHandler了解一下

转载自 mybatis源码阅读(六) ---StatementHandler了解一下 StatementHandler类结构图与接口设计 BaseStatementHandler&#xff1a;一个抽象类&#xff0c;只是实现了一些不涉及具体操作的方法 RoutingStatementHandler&#xff1a;类似路由器&#xff0c;根据配置文件来路由…

nssl1298-网站计划【线段树】

正题 题目大意 若干个区间操作l,rl,rl,r 让答案增加(lr)∗max{ai}(i∈[l..r])(lr)*max\{a_i\}(i\in[l..r])(lr)∗max{ai​}(i∈[l..r]) 并把最大的数变为0 解题思路 线段树&#xff0c;上传最大值时多上传一个位置&#xff0c;然后单点修改。 codecodecode #include<cstd…

输入框限定保留三位小数点

这里用到正则表达式&#xff0c;没输入一个数字会对输入框进行一次事件的触发&#xff0c;检查是否超过三位小数点&#xff0c;超过则进行删除。 你可以3改成2&#xff0c;这样就是保留两位小数点了 <el-input placeholder"请输入商品重量" v-model"baseInfo…

基于OIDC(OpenID Connect)的SSO(纯JS客户端)

在上一篇基于OIDC的SSO的中涉及到了4个Web站点&#xff1a; oidc-server.dev&#xff1a;利用oidc实现的统一认证和授权中心&#xff0c;SSO站点。 oidc-client-hybrid.dev&#xff1a;oidc的一个客户端&#xff0c;采用hybrid模式。 oidc-client-implicit.dev&#xff1a;od…

mybatis源码阅读(七) ---ResultSetHandler了解一下

转载自 mybatis源码阅读(七) ---ResultSetHandler了解一下 1、MetaObject MetaObject用于反射创建对象、反射从对象中获取属性值、反射给对象设置属性值&#xff0c;参数设置和结果封装&#xff0c;用的都是这个MetaObject提供的功能。 public static MetaObject forObject…