MyBatis 源码系列:MyBatis 解析配置文件、二级缓存、SQL

文章目录

    • 解析全局配置文件
    • 二级缓存解析
      • 解析二级缓存
      • 缓存中的调用过程
      • 缓存中使用的设计模式
    • 解析SQL

解析全局配置文件

启动流程分析

String resource = "mybatis-config.xml";
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

通过上面代码发现,创建SqlSessionFactory的代码在SqlSessionFactoryBuilder中,进去一探究竟:

//整个过程就是将配置文件解析成Configration对象,然后创建SqlSessionFactory的过程
//Configuration是SqlSessionFactory的一个内部属性 
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (reader != null) {reader.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}

下面是解析配置文件的核心方法:

  public Configuration parse() {//若已经解析过了 就抛出异常if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;/*** 解析我们的mybatis-config.xml的* 节点* <configuration>* </configuration>*/parseConfiguration(parser.evalNode("/configuration"));return configuration;}/*** 解析我们mybatis-config.xml的 configuration节点* @param root*/private void parseConfiguration(XNode root) {try {// issue #117 read properties first/*** 解析 properties节点*     <properties resource="db.properties" />*     解析到org.apache.ibatis.parsing.XPathParser#variables*          org.apache.ibatis.session.Configuration#variables*/propertiesElement(root.evalNode("properties"));/*** 解析我们的mybatis-config.xml中的settings节点* 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings* <settings><setting name="cacheEnabled" value="true"/><setting name="lazyLoadingEnabled" value="true"/><setting name="mapUnderscoreToCamelCase" value="false"/><setting name="localCacheScope" value="SESSION"/><setting name="jdbcTypeForNull" value="OTHER"/>..............</settings>**/Properties settings = settingsAsProperties(root.evalNode("settings"));/*** 基本没有用过该属性* VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序解析到:org.apache.ibatis.session.Configuration#vfsImpl*/loadCustomVfsImpl(settings);/*** 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。* SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING* 解析到org.apache.ibatis.session.Configuration#logImpl*/loadCustomLogImpl(settings);/*** 解析我们的别名* <typeAliases><typeAlias alias="User" type="com.mcode.entity.User"/></typeAliases><typeAliases><package name="com.mcode.entity"/></typeAliases>解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases除了自定义的,还有内置的*/typeAliasesElement(root.evalNode("typeAliases"));/*** 解析我们的插件(比如分页插件)* mybatis自带的* Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)ParameterHandler (getParameterObject, setParameters)ResultSetHandler (handleResultSets, handleOutputParameters)StatementHandler (prepare, parameterize, batch, update, query)解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors*/pluginsElement(root.evalNode("plugins"));/*** 可以配置  一般不会去设置* 对象工厂 用于反射实例化对象、对象包装工厂、* 反射工厂 用于属性和setter/getter 获取*/objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));// 设置settings 和默认值到configurationsettingsElement(settings);/*** 解析我们的mybatis环境<environments default="dev"><environment id="dev"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="root"/><property name="password" value="Zw726515"/></dataSource></environment></environments>*  解析到:org.apache.ibatis.session.Configuration#environment*  在集成spring情况下由 spring-mybatis提供数据源 和事务工厂*/environmentsElement(root.evalNode("environments"));/*** 解析数据库厂商*     <databaseIdProvider type="DB_VENDOR"><property name="SQL Server" value="sqlserver"/><property name="DB2" value="db2"/><property name="Oracle" value="oracle" /><property name="MySql" value="mysql" /></databaseIdProvider>*  解析到:org.apache.ibatis.session.Configuration#databaseId*/databaseIdProviderElement(root.evalNode("databaseIdProvider"));/*** 解析我们的类型处理器节点* <typeHandlers><typeHandler handler="org.mybatis.example.ExampleTypeHandler"/></typeHandlers>解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap*/typeHandlersElement(root.evalNode("typeHandlers"));/*** 解析我们的mapper*resource:来注册我们的class类路径下的url:来指定我们磁盘下的或者网络资源的class:若注册Mapper不带xml文件的,这里可以直接注册若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径--><mappers><mapper resource="mybatis/mapper/EmployeeMapper.xml"/><mapper class="com.mcode.mapper.EmployeeMapper"></mapper><package name="com.mcode.mapper"></package>--></mappers>* package*     ·解析mapper接口代理工厂(传入需要代理的接口) 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers·解析mapper.xml  最终解析成MappedStatement 到:org.apache.ibatis.session.Configuration#mappedStatements*/mappersElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

image

上面解析流程结束后会生成一个Configration对象,包含所有配置信息,然后会创建一个SqlSessionFactory对象,这个对象包含了Configration对象。

简单总结

对于MyBatis启动的流程(获取SqlSession的过程)这边简单总结下:

  • SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;

image

二级缓存解析

解析二级缓存

解析mapper文件

  private void mappersElement(XNode context) throws Exception {if (context == null) {return;}/*** 获取我们mappers节点下的一个一个的mapper节点*/for (XNode child : context.getChildren()) {/*** 判断我们mapper是不是通过批量注册的* <package name="com.mcode.mapper"></package>*/if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {/*** 判断从classpath下读取我们的mapper* <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>*/String resource = child.getStringAttribute("resource");/*** 判断是不是从我们的网络资源读取(或者本地磁盘得)* <mapper url="D:/mapper/EmployeeMapper.xml"/>*/String url = child.getStringAttribute("url");/*** 解析这种类型(要求接口和xml在同一个包下)* <mapper class="com.mcode.mapper.DeptMapper"></mapper>**/String mapperClass = child.getStringAttribute("class");/*** 我们得mappers节点只配置了* <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>*/if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);/*** 把我们的文件读取出一个流*/try (InputStream inputStream = Resources.getResourceAsStream(resource)) {/*** 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件*/XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,configuration.getSqlFragments());/*** 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)*/mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {        ErrorContext.instance().resource(url);try (InputStream inputStream = Resources.getUrlAsStream(url)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}

解析mapper

  /*** 真正的去解析我们的Mapper.xml(EmployeeMapper.xml)*/public void parse() {/*** 判断当前的Mapper是否被加载过*/if (!configuration.isResourceLoaded(resource)) {/*** 真正的解析我们的 <mapper namespace="com.mcode.mapper.EmployeeMapper">**/configurationElement(parser.evalNode("/mapper"));/*** 把资源保存到我们Configuration中*/configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}public XNode getSqlFragment(String refid) {return sqlFragments.get(refid);}/*** 解析我们的<mapper></mapper>节点* @param context*/private void configurationElement(XNode context) {try {/*** 解析我们的namespace属性* <mapper namespace="com.cmode.mapper.EmployeeMapper">*/String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty");}/*** 保存我们当前的namespace  并且判断接口完全类名==namespace*/builderAssistant.setCurrentNamespace(namespace);/*** 解析我们的缓存引用* 说明我当前的缓存引用和DeptMapper的缓存引用一致* <cache-ref namespace="com.tuling.mapper.DeptMapper"></cache-ref>解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs*/cacheRefElement(context.evalNode("cache-ref"));/*** 解析我们的cache节点* <cache ></cache>解析到:org.apache.ibatis.session.Configuration#cachesorg.apache.ibatis.builder.MapperBuilderAssistant#currentCache*/cacheElement(context.evalNode("cache"));/*** 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)*/parameterMapElement(context.evalNodes("/mapper/parameterMap"));/*** 解析我们的resultMap节点* 解析到:org.apache.ibatis.session.Configuration#resultMaps*    异常 org.apache.ibatis.session.Configuration#incompleteResultMaps**/resultMapElements(context.evalNodes("/mapper/resultMap"));/*** 解析我们通过sql片段*  解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments*   其实等于 org.apache.ibatis.session.Configuration#sqlFragments*   因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了*/sqlElement(context.evalNodes("/mapper/sql"));/*** 解析我们的select | insert |update |delete节点* 解析到org.apache.ibatis.session.Configuration#mappedStatements*/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);}}

解析Cache

  /*** 解析cache* @param context*/private void cacheElement(XNode context) {if (context != null) {//解析cache节点的type属性String type = context.getStringAttribute("type", "PERPETUAL");// 根据别名(或完整限定名)  加载为ClassClass<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);/*获取缓存过期策略:默认是LRULRU – 最近最少使用:移除最长时间不被使用的对象。(默认)FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。*/String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);//flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。Long flushInterval = context.getLongAttribute("flushInterval");//size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。Integer size = context.getIntAttribute("size");//只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 falseboolean readWrite = !context.getBooleanAttribute("readOnly", false);boolean blocking = context.getBooleanAttribute("blocking", false);Properties props = context.getChildrenAsProperties();//把缓存节点加入到Configuration中builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}}

image

缓存中的调用过程

image

缓存中使用的设计模式

image

解析SQL

buildStatementFromContext

/*** 方法实现说明:解析我们得得select|update|delte|insert节点然后* 创建我们得mapperStatment对象* @param list:所有的select|update|delte|insert节点* @param requiredDatabaseId:判断有没有数据库厂商Id*/private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {/*** 循环我们的select|delte|insert|update节点*/for (XNode context : list) {/*** 创建一个xmlStatement的构建器对象*/final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}}

parseStatementNode

 public void parseStatementNode() {/*** 我们的insert|delte|update|select 语句的sqlId*/String id = context.getStringAttribute("id");/*** 判断我们的insert|delte|update|select  节点是否配置了* 数据库厂商标注*/String databaseId = context.getStringAttribute("databaseId");/*** 匹配当前的数据库厂商id是否匹配当前数据源的厂商id*/if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}/*** 获得节点名称:select|insert|update|delete*/String nodeName = context.getNode().getNodeName();/*** 根据nodeName 获得 SqlCommandType枚举*/SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));/*** 判断是不是select语句节点*/boolean isSelect = sqlCommandType == SqlCommandType.SELECT;/***  获取flushCache属性*  默认值为isSelect的反值:查询:flushCache=false   增删改:flushCache=true*/boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);/*** 获取useCache属性* 默认值为isSelect:查询:useCache=true   增删改:useCache=false*/boolean useCache = context.getBooleanAttribute("useCache", isSelect);/*** resultOrdered:  是否需要分组:*  select * from user-->User{id=1, name='User1', groups=[1, 2], roles=[1, 2, 3]}*/boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);/*** 解析我们的sql公用片段*     <select id="qryEmployeeById" resultType="Employee" parameterType="int"><include refid="selectInfo"></include>employee where id=#{id}</select>将 <include refid="selectInfo"></include> 解析成sql语句 放在<select>Node的子节点中*/// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());/*** 解析我们sql节点的参数类型*/String parameterType = context.getStringAttribute("parameterType");// 把参数类型字符串转化为classClass<?> parameterTypeClass = resolveClass(parameterType);/*** 查看sql是否支撑自定义语言* <delete id="delEmployeeById" parameterType="int" lang="mcodeLang"><settings><setting name="defaultScriptingLanguage" value="mcodeLang"/></settings>*/String lang = context.getStringAttribute("lang");/*** 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver*/LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them./*** 解析我们<insert 语句的的selectKey节点, 一般在oracle里面设置自增id*/processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)/*** 我们insert语句 用于主键生成组件*/KeyGenerator keyGenerator;/*** selectById!selectKey* id+!selectKey*/String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;/*** 把我们的命名空间拼接到keyStatementId中* com.mcode.mapper.Employee.saveEmployee!selectKey*/keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);/***<insert id="saveEmployee" parameterType="com.mcode.entity.Employee" useGeneratedKeys="true" keyProperty="id">*判断我们全局的配置类configuration中是否包含以及解析过的主键生成器对象*/if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {/*** 若我们<insert 配置了useGeneratedKeys 那么就取useGeneratedKeys的配置值,* 否者就看我们的mybatis-config.xml配置文件中是配置了* <setting name="useGeneratedKeys" value="true"></setting> 默认是false* 并且判断sql操作类型是否为insert* 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE* 否则就是NoKeyGenerator.INSTANCE*/keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}/*** 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的* sql脚本对象  .  解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析*/SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);/*** STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED*/StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));/*** 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)*/Integer fetchSize = context.getIntAttribute("fetchSize");/*** 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。*/Integer timeout = context.getIntAttribute("timeout");/*** 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置*/String parameterMap = context.getStringAttribute("parameterMap");/*** 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。* 可以使用 resultType 或 resultMap,但不能同时使用*/String resultType = context.getStringAttribute("resultType");/**解析我们查询结果集返回的类型     */Class<?> resultTypeClass = resolveClass(resultType);/*** 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。* 可以使用 resultMap 或 resultType,但不能同时使用。*/String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}/*** 解析 keyProperty  keyColumn 仅适用于 insert 和 update*/String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");/*** 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象*/builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

createSqlSource

  /*** 方法实现说明:创建我们的sqlSource对象* @param configuration:全局配置* @param script:脚本类型* @param parameterType:参数类型*/@Overridepublic SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}

parseScriptNode

 public SqlSource parseScriptNode() {/*** 递归解析-组合设计模式  selectById这个sql元素会解析成*    1层  MixedSqlNode <SELECT>*    2层  WhereSqlNode <WHERE>*    3层  IfSqlNode <IF>*       test="条件表达式"**  contexts= sql语句分: 1.TextSqlNode 带${}   2.StaticTextSqlNode*/MixedSqlNode rootSqlNode = parseDynamicTags(context);SqlSource sqlSource;if (isDynamic) {// 动态Sql源// 动态Sql 就是还需要后续执行时根据传入参数动态解析Sql(因为有<if>等,还要拼接${}sql)//    和参数ParameterMappings   也会在后续执行解析,因为动态条件肯定会有动态参数sqlSource = new DynamicSqlSource(configuration, rootSqlNode);} else {// 静态Sql源  如果没有动态标签(<if>、<where>等) 以及 没有${}  就是静态Sql源// 静态Sql 就是在这里就解析了Sql  和参数ParameterMappings   后续执行就不用解析了sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);}// 其实他们的区别就是动态sql 需要在查询的时候解析 因为有动态sql 和拼接${}//                  静态sql 已经在这里确定好sql. 和参数ParameterMapping,return sqlSource;}

image

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

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

相关文章

探索ESP32 C++ OOP开发:与传统面向过程编程的比较

探索ESP32 OOP开发&#xff1a;与传统面向过程编程的比较 在嵌入式系统开发中&#xff0c;ESP32是一个强大的平台&#xff0c;可以应用于各种项目和应用场景。在编写ESP32代码时&#xff0c;我们可以选择使用面向对象编程&#xff08;OOP&#xff09;的方法&#xff0c;将代码…

数据结构—栈实现前缀表达式的计算

前缀表达式计算 过程分析 中缀表达式&#xff1a;&#xff08;1 5&#xff09;*3 > 前缀表达式&#xff1a;*153 &#xff08;可参考这篇文章&#xff1a;中缀转前缀&#xff09; 第一步&#xff1a;从右至左扫描前缀表达式&#xff08;已存放在字符数组中&#xff09;&a…

termux 玩法(一)

termux基础 termux基础玩法推荐国光写的手册&#xff1a;Termux 高级终端安装使用配置教程 | 国光 (sqlsec.com) termux安装 个人使用F-Droid安装的termux&#xff1a;Termux | F-Droid - Free and Open Source Android App Repository 基础知识 这些基础知识简单了解一下…

自定义模块加载(Python)

加载自定义模块&#xff0c;系统抛出“找不到文件”异常提示信息。 (笔记模板由python脚本于2024年01月28日 12:50:00创建&#xff0c;本篇笔记适合初通Python的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免…

链表——超详细

一、无头单向非循环链表 1.结构&#xff08;两个部分&#xff09;&#xff1a; typedef int SLTDataType; typedef struct SListNode {SLTDataType data;//数据域struct SListNode* next;//指针域 }SLNode; 它只有一个数字域和一个指针域&#xff0c;里面数据域就是所存放的…

万户 ezOFFICE wf_accessory_delete.jsp SQL注入漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

继电器模块详解

继电器&#xff0c;一种常见的电控制装置&#xff0c;其应用几乎无处不在。在家庭生活&#xff0c;继电器被广泛应用于照明系统、电视机、空调等电器设备的控制&#xff1b;在工业领域&#xff0c;它们用于控制电机、泵站、生产线等高功率设备的运行&#xff1b;继电器还在通信…

【论文收集】

Collaborative Diffusion for Multi-Modal Face Generation and Editing https://arxiv.org/abs/2304.10530 code&#xff1a;https://github.com/ziqihuangg/collaborative-diffusion 现有的扩散模型主要集中在单模态控制上&#xff0c;即扩散过程仅由一种状态模态驱动。为…

Docker的使用方式

一、Docker概念 Docker类似于一个轻量的虚拟机。 容器和镜像是Docker中最重要的两个概念&#xff0c;镜像可以保存为tar文件&#xff0c;Dockerfile是配置文件&#xff0c;仓库保存了很多第三方已经做好的镜像。 基本指令 查找镜像 docker search nginx 拉取nginx镜像 do…

搭建Jmeter分布式压测与监控,轻松实践

对于运维工程师来说&#xff0c;需要对自己维护的服务器性能瓶颈了如指掌&#xff0c;比如我当前的架构每秒并发是多少&#xff0c;我服务器最大能接受的并发是多少&#xff0c;是什么导致我的性能有问题&#xff1b;如果当前架构快达到性能瓶颈了&#xff0c;是横向扩容性能提…

香港服务器IP段4c和8c的区别及SEO选择建议

随着互联网的快速发展&#xff0c;服务器IP段的选择对于网站SEO优化至关重要。香港服务器IP段4C和8C是两种常见的IP段&#xff0c;它们在SEO优化中具有不同的特点和优势。本文将详细介绍这两种IP段的区别&#xff0c;并给出相应的SEO选择建议。 一、香港服务器IP段4C和8C的区别…

论文阅读,Domain Specific ML Prefetcher for Accelerating Graph Analytics(一)

目录 一、Article:文献出处&#xff08;方便再次搜索&#xff09; &#xff08;1&#xff09;作者 &#xff08;2&#xff09;文献题目 &#xff08;3&#xff09;文献时间 &#xff08;4&#xff09;引用 二、Data:文献数据&#xff08;总结归纳&#xff0c;方便理解&am…

Pyecharts炫酷热力图:参数详解与实战大揭秘

Pyecharts绘制多种炫酷热力图参数说明代码实战 引言 热力图在数据可视化中是一种强大的工具&#xff0c;可以直观地展示数据的分布情况和变化趋势。Pyecharts是一个基于Echarts的Python可视化库&#xff0c;提供了丰富的图表类型&#xff0c;包括热力图。在本文中&#xff0c…

【JavaSe篇】——封装,static成员,代码块

目录 &#x1f469;&#x1f3fb;‍&#x1f4bb;封装 &#x1f6a9;访问限定符 &#x1f6a9;封装扩展之包 &#x1f449;导入包中的类 &#x1f449;自定义包 &#x1f576;️操作步骤 &#x1f449;包的访问权限控制举例 &#x1f576;️常见的包 &#x1f469;&am…

10V单通道负载开关

概述 EM5220是一款单通道负载开关&#xff0c;具有可编程上升时间和集成输出放电控制。该设备包含一个P沟道NOSFET&#xff0c;可以通过输入进行操作电压范围为4.5V至10V。开关由接通和断开低电平逻辑输入控制&#xff0c;其能够与GPIO信号接口。设备的可编程上升时间可以减少…

Ruff应用:打破传统,IoT技术赋能工业制造数字化转型之路

近年来&#xff0c;随着物联网、大数据、云计算、5G等数字技术的快速应用&#xff0c;工业制造领域正在经历着前所未有的变革。工业4.0时代&#xff0c;各种数字技术与工业制造的结合&#xff0c;不仅提高了工业生产效率、降低运营成本&#xff0c;更是极大地推动了传统工业数字…

ssm跨域方案?

1、过滤器 2、xml配置 <mvc:cors><mvc:mapping path"/**" /> </mvc:cors>3、注解 CrossOrigin(origins “*”) 说明&#xff1a;三种方案&#xff0c;本质都是一样的、只是方式不一样罢了。

Vite+Vue3使用Vue-i18n笔记

一、下载依赖 vue-i18n yarn add vue-i18n创建存放语言文件的目录 以及配置文件的配置 我是在src/lang 新建index.ts、cn.ts、en.ts以及test文件夹其中再分别新建cn.ts以及en.ts /lang/index.ts 用于导出vue-i18n需要的配置对象 import en from "./en.ts"; import…

MySQL的原生API实现插入数据后在可视化工具上不显示的问题解决

显示表中有两行数据&#xff0c;该表也设置了主键和唯一索引 点进表里看却没有数据 问题原因出现在这里&#xff0c;虽然很多常用的数据库连接池都会开启自动提交&#xff0c;但ibatis的SqlSession使用sessionFactory.openSession()创建时&#xff0c;默认的自动提交是false&am…

com.alicp.jetcache.support.CacheEncodeException: Java Encode error 报错解决

目录 一、报错截图&#xff1a;二、报错原因三、解决方式 一、报错截图&#xff1a; Spring boot 整合 JetCache 使用Cached。报错如下&#xff1a; 二、报错原因 带有Cached注解的方法返回值对象没有实现序列化接口&#xff0c;如下图所示 三、解决方式 带有Cached注解的…