MyBatis 源码分析-- SQL请求执行流程( Mapper 接口方法的执行的过程)

前言

前面我们从源码层面梳理了 SqlSessionFactory、SqlSession 的创建过程及 Mapper 获取过程,本篇我们继续分析一下 Mapper 接口方法的执行的过程,也就是 SQL 的执行流程。

Mybatis 相关知识传送门

初识 MyBatis 【MyBatis 核心概念】

MyBatis 源码分析–SqlSessionFactory

MyBatis 源码分析–获取SqlSession

MyBatis 源码分析-- getMapper(获取Mapper)

SQL 请求执行流程

分析 SQL 执行流程之前,先附上一张简单的源码调用链路图,方便后面分析 SQL 的执行流程。

在这里插入图片描述

执行 Mapper 方法

前面分析了,从 SqlSession 中获取的 Mapper 是代理对象,执行 Mapper 接口方法时候,会调用 MapperProxy#invoke 方法。

MapperProxy#invoke 方法源码分析

MapperProxy#invoke 方法会判断执行的方法是否是 Object 类的方法,如果是就无需代理,否则才会走代理的方法,代理的方法有两个默认实现 一个是普通方法 PlainMethodInvoker 和默认方法实现 DefaultMethodInvoker。

//org.apache.ibatis.binding.MapperProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//判断是不是 Object 类的方法  是就直接返回 无需代理  不是则调用缓存的 invoke 方法 有两个实现类 一个是普通方法 PlainMethodInvoker  和默认方法实现 DefaultMethodInvokerreturn Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);} catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable(var5);}
}

MapperProxy#cachedInvoker 方法源码分析

MapperProxy#cachedInvoker 方法会先从缓存中获取 MapperMethodInvoker,如果缓存不存在则穿件一个 MapperMethodInvoker 再加入缓存,创建 MapperMethodInvoker 时候会判断是普通方法还是默认方法。

//org.apache.ibatis.binding.MapperProxy#cachedInvoker
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {//获取 MapperMethodInvokerMapperProxy.MapperMethodInvoker invoker = (MapperProxy.MapperMethodInvoker)this.methodCache.get(method);//缓存中是否存在 不存在 则加入缓存return invoker != null ? invoker : (MapperProxy.MapperMethodInvoker)this.methodCache.computeIfAbsent(method, (m) -> {//是否是默认的if (m.isDefault()) {try {//默认的方法return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {throw new RuntimeException(var4);}} else {//普通方法 new MapperMethod 重点关注return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));}});} catch (RuntimeException var4) {Throwable cause = var4.getCause();throw (Throwable)(cause == null ? var4 : cause);}
}

MapperMethod 创建源码分析

MapperMethod 的创建中做了两个操作,一个是创建了 SqlCommand,还有一个是创建了MethodSignature。

//org.apache.ibatis.binding.MapperMethod#MapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {//创建 SqlCommandthis.command = new MapperMethod.SqlCommand(config, mapperInterface, method);//创建 MethodSignaturethis.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}

SqlCommand 创建源码分析

SqlCommand 的创建其实是从 MappedStatement 中获取 name、SqlCommandType 完成赋值,从这里我们可以看出 MappedStatement 中存储的是 SQL 信息,例如:id、SELECT、UPDATE、DELETE 等 。

//org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {//获取方法名称String methodName = method.getName();//获取方法的 classClass<?> declaringClass = method.getDeclaringClass();//得到 MappedStatementMappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);//MappedStatement 为空判断if (ms == null) {//是否是 Flush 注解标记的接口if (method.getAnnotation(Flush.class) == null) {//没有找到对应的 statementthrow new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);}this.name = null;//SqlCommandType FLUSHthis.type = SqlCommandType.FLUSH;} else {//有 MappedStatement this.name = ms.getId();this.type = ms.getSqlCommandType();//SqlCommandType.UNKNOWN 判断if (this.type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + this.name);}}}//org.apache.ibatis.binding.MapperMethod.SqlCommand#resolveMappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {//接口类名 + . 方法名String statementId = mapperInterface.getName() + "." + methodName;//在 mappedStatements 中是否存在 解析xml 时候存入的if (configuration.hasStatement(statementId)) {//找到返回return configuration.getMappedStatement(statementId);} else if (mapperInterface.equals(declaringClass)) {return null;} else {Class[] var6 = mapperInterface.getInterfaces();int var7 = var6.length;//查询父接口for(int var8 = 0; var8 < var7; ++var8) {Class<?> superInterface = var6[var8];if (declaringClass.isAssignableFrom(superInterface)) {MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);if (ms != null) {return ms;}}}return null;}
}

SqlCommandType

public enum SqlCommandType {UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH;private SqlCommandType() {}
}

MethodSignature#MethodSignature 源码分析

MethodSignature#MethodSignature 方法构造了方法签名,完成 MethodSignature 的初始化,方法签名初始化完成之后,MapperMethod 实例就创建完成了,回到了 MapperProxy 的 invoke 方法,准备开始执行 execute 方法。

//org.apache.ibatis.binding.MapperMethod.MethodSignature#MethodSignature
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {//解析返回值类型Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);//设置返回值类型if (resolvedReturnType instanceof Class) {this.returnType = (Class)resolvedReturnType;} else if (resolvedReturnType instanceof ParameterizedType) {this.returnType = (Class)((ParameterizedType)resolvedReturnType).getRawType();} else {this.returnType = method.getReturnType();}//返回voidthis.returnsVoid = Void.TYPE.equals(this.returnType);//返回多条this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();//返回游标this.returnsCursor = Cursor.class.equals(this.returnType);//是否返回Optionalthis.returnsOptional = Optional.class.equals(this.returnType);//mapKey的值this.mapKey = this.getMapKey(method);//是否map查询this.returnsMap = this.mapKey != null;//RowBounds 索引  RowBounds 是分页用的 记录 RowBounds 是第几个参数 this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);//ResultHandler 索引 resultHandler 是结果处理器 ResultHandler 是第几个参数this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);//参数名称解析器this.paramNameResolver = new ParamNameResolver(configuration, method);
}

MapperProxy.PlainMethodInvoker 源码分析

前面我们分析了 MapperProxy.PlainMethodInvoker 包装了 MapperMethod,PlainMethodInvoker 的 invoke 方法最终会调用 MapperMethod 的 invoke 方法,我们接着分析 MapperMethod#execute 方法。

private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {this.mapperMethod = mapperMethod;}public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {//调用 MapperMethod#execute 方法return this.mapperMethod.execute(sqlSession, args);}
}

MapperMethod#execute 源码分析

MapperMethod#execute 方法会根据不用的 Type 和不同的返回值类型,调用不同的方法执行 SQL。

//org.apache.ibatis.binding.org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {//返回结果Object result;//参数Object param;//sql 语句类型 insert update delete selectswitch(this.command.getType()) {case INSERT://解析参数param = this.method.convertArgsToSqlCommandParam(args);//rowCountResult 包装返回结果 sqlSession.insert 调用的是 DefauleSqlSession 的inset 方法 最终调用的的是 executor 的 update方法result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));break;case UPDATE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.update(this.command.getName(), param));break;case DELETE:param = this.method.convertArgsToSqlCommandParam(args);result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));break;case SELECT://返回值类型判断if (this.method.returnsVoid() && this.method.hasResultHandler()) {//没有返回值this.executeWithResultHandler(sqlSession, args);result = null;} else if (this.method.returnsMany()) {//返回多个result = this.executeForMany(sqlSession, args);} else if (this.method.returnsMap()) {//返回 Mapresult = this.executeForMap(sqlSession, args);} else if (this.method.returnsCursor()) {//返回游标result = this.executeForCursor(sqlSession, args);} else {//返回值为 单一对象的方法//将方法参数转换为 SQL 参数param = this.method.convertArgsToSqlCommandParam(args);//selectOne 语句的执行入口result = sqlSession.selectOne(this.command.getName(), param);if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + this.command.getName());}//返回值判断if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");} else {return result;}
}//org.apache.ibatis.session.defaults.DefaultSqlSession#insert(java.lang.String, java.lang.Object)
public int insert(String statement, Object parameter) {return this.update(statement, parameter);
}//org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)
public int update(String statement, Object parameter) {int var4;try {this.dirty = true;MappedStatement ms = this.configuration.getMappedStatement(statement);var4 = this.executor.update(ms, this.wrapCollection(parameter));} catch (Exception var8) {throw ExceptionFactory.wrapException("Error updating database.  Cause: " + var8, var8);} finally {ErrorContext.instance().reset();}return var4;
}

MethodSignature#convertArgsToSqlCommandParam 源码分析

MethodSignature#convertArgsToSqlCommandParam 方法主要是对参数进行解析。

//org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam
public Object convertArgsToSqlCommandParam(Object[] args) {return this.paramNameResolver.getNamedParams(args);
}//org.apache.ibatis.reflection.ParamNameResolver#getNamedParams
public Object getNamedParams(Object[] args) {int paramCount = this.names.size();if (args != null && paramCount != 0) {if (!this.hasParamAnnotation && paramCount == 1) {//一个参数Object value = args[(Integer)this.names.firstKey()];return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);} else {//多个参数Map<String, Object> param = new ParamMap();int i = 0;//遍历加入 Mapfor(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {Entry<Integer, String> entry = (Entry)var5.next();param.put(entry.getValue(), args[(Integer)entry.getKey()]);//通用参数名String genericParamName = "param" + (i + 1);if (!this.names.containsValue(genericParamName)) {//不含有通用参数名才添加param.put(genericParamName, args[(Integer)entry.getKey()]);}}return param;}} else {//没有参数return null;}
}//处理集合参数
//org.apache.ibatis.reflection.ParamNameResolver#wrapToMapIfCollection
public static Object wrapToMapIfCollection(Object object, String actualParamName) {ParamMap map;//是否是集合类型if (object instanceof Collection) {map = new ParamMap();map.put("collection", object);if (object instanceof List) {map.put("list", object);}Optional.ofNullable(actualParamName).ifPresent((name) -> {map.put(name, object);});return map;} else if (object != null && object.getClass().isArray()) {//是否 array 参数map = new ParamMap();map.put("array", object);Optional.ofNullable(actualParamName).ifPresent((name) -> {map.put(name, object);});return map;} else {//非集合数组参数直接返回return object;}
}

DefaultSqlSession#selectOne 源码分析

DefaultSqlSession#selectOne 方法实际调用的也是 selectList 方法。

//org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
public <T> T selectOne(String statement, Object parameter) {//调用的也是 selectListList<T> list = this.selectList(statement, parameter);//结果集判断 if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}
}

DefaultSqlSession#selectList 源码分析

DefaultSqlSession#selectList 方法调用了执行器 Executor 执行 SQL 语句。

//org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)
public <E> List<E> selectList(String statement, Object parameter) {//返回结果的最大值 int 默认值 2147483647return this.selectList(statement, parameter, RowBounds.DEFAULT);
}//DefaultSqlSession#selectList 方法调用了执行器 Executor 执行 SQL 语句。
//org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {List var6;try {//获取 MappedStatement MappedStatement 中有已经处理的 sql 信息MappedStatement ms = this.configuration.getMappedStatement(statement);//最终调用执行器的 query  RowBounds 是用来逻辑分页  按照条件将数据从数据库查询到内存中  在内存中进行分页 wrapCollection 处理集合或者数组参数List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);var6 = result;} catch (Exception var10) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);} finally {ErrorContext.instance().reset();}return var6;
}

CachingExecutor#query 源码分析

CachingExecutor#query 方法会从 MappedStatement 中获取 SQL 信息,创建缓存 key 并执行查询,Configuration 中 cacheEnabled 属性值默认为 true,因此会执行 CachingExecutor 的 query方法。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {//获取 SQL 基本信息BoundSql boundSql = ms.getBoundSql(parameterObject);//创建缓存 keyCacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);//执行查询return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

CachingExecutor#query 源码分析

CachingExecutor#query 会判断是否使用了缓存,如果允许使用缓存会先从二级缓存查询,二级缓存中查询不到才会去查询一级缓存或者数据库,如果二级缓存为空或者不允许使用缓存就会直接去查询一级缓存或者数据库。

//org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//获取 二级缓存Cache cache = ms.getCache();//为空判断if (cache != null) {//是否刷新缓存//select 时候 flushCacheRequired 默认是false 不清除二级缓存 //insert update delete 时候 flushCacheRequired 是 true 会清除二级缓存this.flushCacheIfRequired(ms);//如果使用了缓存  ResultHandler 不为空 if (ms.isUseCache() && resultHandler == null) {//对存储过程的处理 确保没有存储过程 如果有存储过程 就报错this.ensureNoOutParams(ms, parameterObject, boundSql);//从二级缓存中查询数据List<E> list = (List)this.tcm.getObject(cache, key);//结果为空 判断if (list == null) {//为空 BaseExexutor 执行查询数据库list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);//加入二级缓存this.tcm.putObject(cache, key, list);}return list;}}//缓存为空 BaseExexutor 直接查询数据库return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

BaseExecutor#query 源码分析

BaseExecutor#query 方法会先去一级缓存查询数据,一级缓存中查询不到数据才会去查询数据库的数据,同时也会做一些清除一级缓存的判断。

//org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//错误上下文ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());//执行器关闭 抛出异常if (this.closed) {throw new ExecutorException("Executor was closed.");} else {//queryStack 查询堆栈 防止递归查询重复处理缓存  是否刷新缓存 flushCacheRequired 是 true 清除一级缓存if (this.queryStack == 0 && ms.isFlushCacheRequired()) {//清除一级缓存this.clearLocalCache();}List list;try {//查询堆栈 +1++this.queryStack;//从一级缓存中获取数据list = resultHandler == null ? (List)this.localCache.getObject(key) : null;if (list != null) {//从一级缓存中获取数据this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {//从数据库查询数据list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {//查询堆栈-1--this.queryStack;}if (this.queryStack == 0) {Iterator i$ = this.deferredLoads.iterator();while(i$.hasNext()) {BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();deferredLoad.load();}this.deferredLoads.clear();if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {this.clearLocalCache();}}return list;}
}

BaseExecutor#queryFromDatabase 源码分析

BaseExecutor#queryFromDatabase 方法会去查询数据库,并将查询结果加入一级缓存。

//org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//一级缓存 存储一个占位符this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);List list;try {//执行数据库查询list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {//清除占位符缓存this.localCache.removeObject(key);}//存入一级缓存this.localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {this.localOutputParameterCache.putObject(key, parameter);}return list;
}

SimpleExecutor#doQuery 源码分析

SimpleExecutor#doQuery 方法通过 MappedStatement(SQL语句信息,包含 SQL、参数、返回值等)获取 Configuration,创建 StatementHandler(封装 JDBC 操作)。

//org.apache.ibatis.executor.SimpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;List var9;try {//获取 Configuration 对象 Configuration configuration = ms.getConfiguration();//创建 StatementHandler 对象StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//获取 Statementstmt = this.prepareStatement(handler, ms.getStatementLog());//执行查询var9 = handler.query(stmt, resultHandler);} finally {//关闭 statementthis.closeStatement(stmt);}return var9;
}

SimpleExecutor#prepareStatement 源码分析

SimpleExecutor#prepareStatement 方法获取了数据库连接,创建 Statement,通过 StatementHandler 处理相关参数,最终返回 Statement。

//org.apache.ibatis.executor.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {//获取数据库连接 会从事务管理器中获取连接Connection connection = this.getConnection(statementLog);//创建 Statement PreparedStatementStatement stmt = handler.prepare(connection);//通过 ParameterHandler 设置参数handler.parameterize(stmt);return stmt;
}

PreparedStatementHandler#query 源码分析

PreparedStatementHandler#query 方法通过 PreparedStatement 执行查询,ResultHandler 获取结果集,至此,一个简单的 SQL 查询结束了。

//org.apache.ibatis.executor.statement.PreparedStatementHandler#query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement)statement;//执行ps.execute();//ResultHandler 获取结果集return this.resultSetHandler.handleResultSets(ps);
}

至此,selectOne 的执行过程分析结束,其他查询的执行过程大同小异。

欢迎提出建议及对错误的地方指出纠正。

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

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

相关文章

Anaconda3 常用命令及配置

1、Anaconda是什么? 2、conda常用命令 系统环境&#xff1a;windows10 Anaconda版本&#xff1a;Anaconda3-2024.02-1-Windows-x86_64 2.1、虚拟环境管理 1、查看虚拟环境 conda env list conda info -e 2、创建虚拟环境 # 创建名为 pyenv 的虚拟环境 conda create --na…

计算机软件著作权申请流程及费用_快速登记_经验分享收藏级教程

最近需要申请计算机软件著作权&#xff0c;申请流程走了一遍&#xff0c;整理了分享给大家。软件著作权申请流程及费用&#xff0c;软著快速登记、软著材料及问题解答FAQ&#xff0c;阿里云百科阿里云计算机软件著作权登记20天下证&#xff0c;那么如何申请阿里云软件著作权登记…

MTK7628+MT7612 加PA定频数据

1、硬件型号TR726A5G121-DPA PC9.02.0017。如下所示&#xff1a; 2、WIFI5.8 AC模式 42&#xff08;5120MHz&#xff09;信道&#xff0c;80带宽 3、WIFI5.8 AC模式 38&#xff08;5190MHz&#xff09;信道&#xff0c;40带宽 4、WIFI5.8 AC模式 36&#xff08;5180 MHz&…

股票分析学习

库&#xff1a; pandas to_datetime:它可以处理各种格式的日期和时间数据&#xff0c;并将其统一转换为 Pandas 可以理解和操作的内部日期时间格式。 matplotlib.pyplot 用户可以轻松地创建各种静态、动态、交互式和 3D 图形。 1. 绘制线图&#xff08;plot()&#xff09; …

FEP耐酸碱耐高温可定制滴瓶60ml透明四氟瓶F46滴加瓶

FEP滴瓶&#xff1a;又叫聚全氟乙丙烯滴瓶&#xff0c;特氟龙滴瓶。广泛应用于痕量分析、超痕量分析、ICP-MS分析、同位素分析等实验。 主要特性如下&#xff1a; 1.耐高低温&#xff1a;使用温度可达-200&#xff5e;205℃&#xff1b; 2.材质为高纯实验级进口TeflonFEP加工…

虚拟机链接不上usb

传输速度慢 记得换一个支持usb3.0的口和支持usb3.0的线

C语言从入门到进阶(15万字总结)

前言&#xff1a; 《C语言从入门到进阶》这本书可是作者呕心沥血之作&#xff0c;建议零售价1元&#xff0c;当然这里开个玩笑。 本篇博客可是作者之前写的所有C语言笔记博客的集结&#xff0c;本篇博客不止有知识点&#xff0c;还有一部分代码练习。 有人可能会问&#xff…

运维技术栈总结

文章目录 Linux CommandBasecd/lschmod/chown/chgrpvi/vimscptarsudf Installrpmyumdeb/apt Filtertailgrepawkfindnetstatechotelnetwhereistouch/mkdirgzip/rar/tar Statistics Linux MonitorCPUtophtopsar Memoryfreevmstat I/Oiostatpidstatiotop Networknetstatiftoptcpdu…

认识Retrieval Augmented Generation(RAG)

什么是RAG&#xff1f; Retrieval-Augmented Generation (RAG) 是一种结合信息检索和生成式AI技术的框架。它通过从外部数据源检索信息&#xff0c;增强语言模型&#xff08;如GPT-3&#xff09;的生成能力&#xff0c;从而提供更加准确和相关的回答。 RAG的组成部分 信息检…

用腾讯云语音合成(TTS)批量生成英语绘本的朗读音频

孩子进行英语启蒙&#xff0c;需要看很多英语绘本&#xff0c;而且要听配套的音频来练听力。但有些英语绘本是没有对应音频的&#xff0c;下面简单几步&#xff0c;就可以将任意英语绘本制作出对应的英语朗读音频。 先到电子书资源网站搜索这个绘本名称&#xff0c;如果有电子…

三.iOS核心动画 - 关于图层几何(frame,bounds,transform,position)

引言 关于UIView的布局有一个经常被问到的问题&#xff0c;frame和bounds有什么区别&#xff0c;同样CALayer也有frame和bounds这两个属性&#xff0c;还有一个与UIView的center对应的position属性&#xff0c;本篇博客我们就来详细的探讨一下图层中的frame和bounds到底有什么…

Python酷库之旅-第三方库openpyxl(07)

目录 一、 openpyxl库的由来 1、背景 2、起源 3、发展 4、特点 4-1、支持.xlsx格式 4-2、读写Excel文件 4-3、操作单元格 4-4、创建和修改工作表 4-5、样式设置 4-6、图表和公式 4-7、支持数字和日期格式 二、openpyxl库的优缺点 1、优点 1-1、支持现代Excel格式…

十大经典排序算法——选择排序和冒泡排序

一、选择排序 1.基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据全部排完。 2.直接选择排序 (1) 在元素集合arr[i] — arr[n - 1]中选择关键妈的最大&#xff08;小…

高考十字路口:24年考生如何权衡专业与学校的抉择?

文章目录 每日一句正能量前言专业解析理工科专业商科专业人文社科专业艺术与设计专业个人经验与思考过程结论 名校效应分析名校声誉与品牌效应资源获取学术氛围就业优势个人发展结论 好专业和好学校的权衡个人职业目标行业需求教育质量资源和机会学术氛围就业优势经济和地理位置…

嵌入式学习——数据结构(单向无头链表)——day46

1. 数据结构 1.1 定义 数据结构是指计算机中数据的组织、管理和存储方式。它不仅包括数据元素的存储方式&#xff0c;还包括数据元素之间的关系&#xff0c;以及对数据进行操作的方法和算法。数据结构的选择和设计直接影响算法的效率和程序的性能&#xff0c;是计算机科学与编…

说一说三大运营商的流量类型,看完就知道该怎么选运营商了!

说一说三大运营商的流量类型&#xff0c;看完就知道该怎么选运营商了&#xff1f;目前三大运营商的流量类型大致分为通用流量和定向流量&#xff0c;比如&#xff1a; 中国电信&#xff1a;通用流量定向流量 电信推出的套餐通常由通用流量定向流量所组成&#xff0c;通用流量…

【Python时序预测系列】基于LSTM实现单变量时序序列多步预测(案例+源码)

这是我的第307篇原创文章。 一、引言 单站点单变量输入单变量输出多步预测问题----基于LSTM实现。 单输入就是输入1个特征变量 单输出就是预测出1个标签的结果 多步就是利用过去N天预测未来M天的结果 二、实现过程 2.1 读取数据集 # 读取数据集 data pd.read_csv(data.c…

HTML5文旅文化旅游网站模板源码

文章目录 1.设计来源文旅宣传1.1 登录界面演示1.2 注册界面演示1.3 首页界面演示1.4 文旅之行界面演示1.5 文旅之行文章内容界面演示1.6 关于我们界面演示1.7 文旅博客界面演示1.8 文旅博客文章内容界面演示1.9 联系我们界面演示 2.效果和源码2.1 动态效果2.2 源代码2.3 源码目…

笔记本电脑屏幕模糊?6招恢复屏幕清晰!

在数字化时代的浪潮中&#xff0c;笔记本电脑已成为我们生活、学习和工作中不可或缺的一部分。然而&#xff0c;当那曾经清晰明亮的屏幕逐渐变得模糊不清时&#xff0c;无疑给我们的使用体验蒙上了一层阴影。屏幕模糊不仅影响视觉舒适度&#xff0c;更可能对我们的工作效率和眼…

【AI大模型】驱动的未来:穿戴设备如何革新血液、皮肤检测与营养健康管理

文章目录 1. 引言2. 现状与挑战3. AI大模型与穿戴设备概述4. 数据采集与预处理4.1 数据集成与增强4.2 数据清洗与异常检测 5. 模型架构与训练5.1 高级模型架构5.2 模型训练与调优 6. 个性化营养建议系统6.1 营养建议生成优化6.2 用户反馈与系统优化 7. 关键血液成分与健康状况评…