一条 SQL 是如何在 MyBatis 中执行的

前言

MyBatis 执行 SQL 的核心接口为 SqlSession 接口,该接口提供了一些 CURD 及控制事务的方法,另外还可以通过 SqlSession 先获取 Mapper 接口的实例,然后通过 Mapper 接口执行 SQL,Mapper 接口方法的执行最终还是委托到 SqlSession 中的方法。因此可以由 SqlSession 入手分析 SQL 执行流程。由于本篇文章内容较多,感兴趣的小伙伴可以先收藏,待空闲时间耐心阅读,或直接翻到最后查看总结。

SQL 执行流程分析

MyBatis 中的 SQL 都是由 SqlSession 进行执行,由于日常工作中使用的 SQL 类型多为查询,并且 MyBatis 中的查询也最为复杂,因此本篇以 SqlSession#selectList(String, Object, RowBounds) 作为入口进行分析,中间穿插 SqlSession 的其他 API 介绍,重要的组件将在后面的章节中单独列出。

public interface SqlSession extends Closeable {
    <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);  
}

#selectList 的方法定义如上所示,该方法查询数据库然后将结果转换为用户需要的类型。各参数的具体含义如下。

statement:表示 SQL 语句的标识,Mapper 接口中的方法调用时会使用 接口全限定名. 方法名,对应 Mapper xml 配置中 mapper 节点 namespace.select 节点 id。

parameter:MyBatis SQL 语句中的参数,可以是原生类型或原生类型的 包装类,可以是 Map ,也可以是其他的 Object,如果 Mapper 接口方法中包含多个参数将转换为 Map,MyBatis 取 Map 或 Object 中的字段值替换 Mapper xml 文件中的 ${paramName} 或将 #{paramName} 指定的 SQL 参数设置为对应的字段值。

rowBounds:分页信息,包含 offset 和 limit ,MyBatis 在内存中对返回的结果进行分页。

了解方法的功能后,我们再看方法的实现,MyBatis 中 SqlSession 默认的实现为 DefaultSqlSession,跟踪源码。

public class DefaultSqlSession implements SqlSession {
    // Mybatis 配置
    private final Configuration configuration;
    // 执行器
    private final Executor executor;

    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}

SqlSession 进行数据库查询时先从配置中获取表示 SQL 语句的 MappedStatement,然后使用执行器 Executor 进行执行。调用的 Executor#query 方法定义如下。

public interface Executor {
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
}

Executor 执行查询时多了一个新的参数 ResultHandler,它用于处理每一行数据库记录对应的 Java 对象,例如可以将结果保存到 List 或者 Map 中。Executor 作为接口具有多个实现,CachingExecutor 和其他 Executor 相比仅多了 Statement 级别缓存的支持,因此我们跟踪 BaseExecutor#query 方法的实现。

public abstract class BaseExecutor implements Executor {
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
}

BaseExecutor 先使用 MappedStatement 获取到 BoundSql 的实例,然后创建了表示当前查询的 CacheKey,最后调用了另一个 #query 方法。

BoundSql:MappedStatement 包含 MyBatis 执行 SQL 需要的完整元数据,如结果映射、参数映射、动态 SQL 等,MappedStatement 将动态 SQL 解析后生成 BoundSql,BoundSql 仅包含最终执行的 SQL 及参数信息。

CacheKey:MyBatis 可以将每个 SQL 的查询结果缓存下来,CacheKey 就是用来表示缓存的 key 值,它由 SQL、参数、分页等构成,当下次使用相同的条件查询数据库时可以优先从缓存获取到查询结果。

了解完这些参数后再看调用的 #query 方法。

public abstract class BaseExecutor implements Executor {
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ... 省略校验及缓存处理代码
        List<E> list;
        try {
            queryStack++;
            // 优先从缓存获取
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                // 处理存储过程 OUT 参数
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 缓存中没有数据,从数据库中查询
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        ... 省略缓存处理相关代码
        return list;
    }
}

为了将重点放在主要流程,上面的代码省略了部分缓存处理的方法,关于缓存处理将在后面单独分析。#query 方法优先从缓存中获取查询结果,如果没有获取到则会从数据库进行查询,再看数据库查询的代码。

public abstract class BaseExecutor implements Executor {

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 执行数据库查询
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            localCache.removeObject(key);
        }
        // 将查询结果缓存
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }
    
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
        throws SQLException;    
}

#queryFromDatabase 调用 #doQuery 方法进行数据库查询,然后将查询结果缓存下来。#doQuery 是一个抽象方法,我们看其在默认使用的 SimpleExecutor 中的实现。

public class SimpleExecutor extends BaseExecutor {
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 创建 Statement,设置 SQL 参数
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 使用 StatementHandler 执行查询
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
    // 准备好 Statement
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        // 创建 Statement
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 设置 SQL 参数
        handler.parameterize(stmt);
        return stmt;
    }
}

到了这里,终于看到了熟悉的 JDBC API。#doQuery 方法先使用配置创建了一个 StatementHandler,使用 StatementHandler 创建 Statement 并设置了 SQL 的参数后就开始调用 #StatementHandler#query 执行数据库查询。StatementHandler 用于创建 StatementHandler、设置参数、执行 SQL,如果没有指定则使用

PreparedStatementHandler。

先看 StatementHandler 创建 Statement 的方法 #prepared。

public abstract class BaseStatementHandler implements StatementHandler {
    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
            // 实例化 Statement
            statement = instantiateStatement(connection);
            // 设置 Statement 的数据库参数
            setStatementTimeout(statement, transactionTimeout);
            setFetchSize(statement);
            return statement;
        } catch (SQLException e) {
            closeStatement(statement);
            throw e;
        } catch (Exception e) {
            closeStatement(statement);
            throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
        }
    }
    // 实例化 Statement
    protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
}

StatementHandler#prepared 方法由基类 BaseStatementHandler 实现,先调用了模板方法 #instantiateStatement 实例化 Statement,然后设置 Statement,模板方法由具体的子类实现,如 PreparedStatementHandler 会实例化出 PreparedStatement。

再跟踪 StatementHandler 的实现 PreparedStatementHandler 设置参数的 #parameterize 方法。

public class PreparedStatementHandler extends BaseStatementHandler {
    @Override
    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
    }
}

由于 PreparedStatement 需要设置参数,因此这里 PreparedStatementHandler 将设置参数的动作委托给 ParameterHandler 进行处理。

再看 StatementHandler 查询数据的方法 StatementHandler#query 的实现。

public class PreparedStatementHandler extends BaseStatementHandler {
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.handleResultSets(ps);
    }
}

PreparedStatementHandler 先调用 PreparedStatement#execute 方法执行 SQL,然后使用 ResultSetHandler 将查询结果转换为需要的类型。ResultSetHandler 会根据 resultMap 将数据库记录转换为 Mapper 接口方法返回的对象,由于其内部实现比较复杂,这里暂不进行分析。

MappedStatement

概念理解:根据 CURD 四种 SQL 类型,Mapper xml 文件中操作数据库的节点分为 insert、update、select、delete 四种,对应到 Mapper 接口方法上可以使用的注解则为 @Insert、@Update、@Select、@Delete,MappedStatement 表示这四种语句的元数据,MyBatis 将其保存在 Configuration 中。

解析存储:MyBatis 解析 Mapper xml 文件时会使用 xml 节点中的元数据构建 MappedStatement 实例然后添加到 Configuration,也可以直接将 Mapper 接口直接添加到 Configuration,此时会使用 Mapper 接口中的注解信息构建 MappedStatement 然后添加到 Configuration。

源码位置:参见 XMLMapperBuilder#buildStatementFromContext(List)、MapperAnnotationBuilder#parseStatement

Executor

概念理解:Executor 接口是 SQL 的执行器,它根据 SQL 语句的抽象 MappedStatement 及参数操作数据库,返回操作结果,如果进行数据库查询还可以将结果转换为用户期望的类型。

配置 Executor:通常情况下在 MyBatis 中不需要显式指定具体 Executor,如果需要指定则有以下两种方式。

xml 配置文件中 /configuration/settings 节点下指定 defaultExecutorType 的值来配置默认的 Executor。

通过 Configuration#newExecutor(Transaction, ExecutorType) 方法创建 Executor 的实例。

Executor 接口定义:Executor 接口定义如下。

public interface Executor {
    // 数据库新增、修改、或删除
    int update(MappedStatement ms, Object parameter) throws SQLException;
    // 数据库查询
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
    // 支持批处理的 Executor 批量执行 SQL
    List<BatchResult> flushStatements() throws SQLException;
    // 事务管理
    void commit(boolean required) throws SQLException;
    void rollback(boolean required) throws SQLException;
    Transaction getTransaction();    
    // 从缓存中加载对象的属性值,或记录要从缓存中获取的属性
    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
    // 连接管理
    void close(boolean forceRollback);
    boolean isClosed();
    // 设置当前 Executor 的包装器
    void setExecutorWrapper(Executor executor);   
}

Executor 实现:Executor 作为一个接口在 MyBatis 中有多种实现,类图如下。

BaseExecutor:Executor 的基类。

SimpleExecutor:MyBatis 中默认的 Executor。

ReuseExecutor:复用 Statement 的 Executor,对于相同的 SQL,使用的是同一个 Statement。

BatchExecutor:支持批处理的 Executor,每次执行 #update 方法时会将 SQL 添加到 Statement 中,直到调用 #flushStatements 方法开始提交到数据库执行。

CachingExecutor:作为其他 Executor 的包装器,支持 Statement 级别的缓存,其他 Executor 仅支持 Session 级别的缓存。

ResultHandler

ResultHandler 用于处理 MyBatis 将某一条数据库记录转换成的 Java 对象,通常会将转换结果保存到其内部,待使用时再获取。其接口定义如下。

public interface ResultHandler<T> {
     // 处理每一行对应的值
    void handleResult(ResultContext<? extends T> resultContext);
}

接口中只有一个方法,根据结果 (即数据库单行记录对应的 Java 对象) 的上下文处理结果。ResultHandler 在 MyBatis 中的实现有两个,具体如下。

DefaultResultHandler:将结果存储至内部的 List,Mapper 接口方法返回类型为 List 时使用。

DefaultMapResultHandler:将结果存储至内部的 Map 中,Mapper 接口方法返回类型为 Map 时使用,此时需要在 Mapper 方法上使用 @MapKey 注解指定 key 使用结果的哪个属性。

StatementHandler

StatementHandler 表示 JDBC 中 Statement 的处理器,用于创建 Statement、设置 Statement 中的 SQL 参数、执行 SQL,接口定义如下。

public interface StatementHandler {
    // 创建 Statement,并设置数据库相关参数
    Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
    // 设置 Statement 中的 SQL 参数
    void parameterize(Statement statement) throws SQLException;  
    // 将 SQL 添加到批量执行列表中
    void batch(Statement statement) throws SQLException; 
    // 执行 添加、更新、删除 SQL
    int update(Statement statement) throws SQLException;  
    // 执行查询
    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;         
    <E> Cursor<E> queryCursor(Statement statement) throws SQLException;  
    // 获取 SQL 信息
    BoundSql getBoundSql();
    // 获取参数处理器
    ParameterHandler getParameterHandler();            
}

根据不同的 Statement 类型,StatementHandler 具有不同的实现,其设计和 Executor 很类似,具体如下。

BaseStatementHandler:抽象的 StatementHandler 基类,提供子类的通用实现。

SimpleStatementHandler:简单 StatementHandler,处理普通的 Statement。

PreparedStatementHandler:支持预处理的 StatementHandler,处理 PreparedStatement。

CalableStatementHandler:支持存储过程的 StatementHandler,处理 CallableStatement。

RoutingStatementHandler:其他 StatementHandler 的装饰器,根据 Statement 的类型委托给其他 StatementHandler 做具体的处理。

ParameterHandler

ParameterHandler 是 SQL 参数的处理器,用于设置 SQL 中的参数。其定义比较简单,具体如下,它只有一个默认的实现 DefaultParameterHandler。

public interface ParameterHandler {
    // 获取参数对象,该对象包含 SQL 中可用的参数
    Object getParameterObject();
    
    // 设置 SQL 参数
    void setParameters(PreparedStatement ps) throws SQLException;
}
ResultSetHandler

ResultSetHandler 是 ResultSet 的处理器,当 Statement 执行 SQL 之后,就会使用 ResultSetHandler 处理产生的 ResultSet,ResultHandler 会根据 Mapper xml 文件中定义的 resultMap 或 resultType 将数据库记录转换为 Mapper 接口方法的返回值类型。对于每一行转换为的 Java 对象,使用 ResultHandler 进行处理。该接口默认的实现是 DefaultResultSetHandler ,该接口定义如下。

public interface ResultSetHandler {
    // 处理 ResultSet 为 List
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    // 处理 ResultSet 为 Cursor
    <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
    // 处理存储过程 OUT 参数
    void handleOutputParameters(CallableStatement cs) throws SQLException;    
}
总结

前面以 SqlSession 的方法为入口,分析了 SQL 在 MyBatis 内部执行的代码,并对重要的 API 进行了介绍,这里使用文字的方式进行总结整个过程。

首先我们会使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory。期间 MyBatis 会解析 Mapper xml 文件或注解,生成 SQL 语句元数据 MappedStatement,并保存到 Configuration 中。

使用 SqlSessionFactory 获取 SqlSession,默认的 SqlSession 是 DefaultSqlSession。

使用 SqlSession 获取 Mapper 接口实现,或直接执行 SqlSession 其他方法操作数据库。

SqlSession 根据语句的标识从 Configuration 中获取 MappedStatement,然后委托 Executor 操作数据库。

Executor 优先从缓存中获取数据,如果缓存中没有则执行数据库查询,对于 update 操作会先刷新缓存。

Executor 使用 Configuration 创建出 Statement 的处理器 StatementHandler,委托 StatementHandler 执行数据库操作。

StatementHandler 先实例化出 Statement,然后使用 ParameterHandler 设置 SQL 的参数,最后执行 Statement。

StatementHandler 委托 ResultSetHandler 处理结果集。

ResultSetHandler 处理结果集,根据 resultMap 或 resultType 将每一行数据库的记录转换为 Java 对象,然后将 Java 对象交由 ResultHandler 处理,最后转换为 Mapper 接口方法的返回类型。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

*LEEDCODE 73矩阵置零

![在这里插入代码片](https://img-blog.csdnimg.cn/ab1d7d4b9d5046d8900de430249be3bf.png)1 0 0 替换两个列表 2 记录时 0 0 已经是半改好的状态

【QT】绘图设备

绘图设备是指继承QPainterDevice的子类。Qt提供了很多这样的类&#xff0c;例如QPixmap、QBitmap、QImage和 QPicture。其中&#xff0c; QPixmap专门为图像在屏幕上的显示做了优化QBitmap是QPixmap的一个子类&#xff0c;它的色深限定为1&#xff0c;可以使用 QPixmap的isQBi…

AndroidPicker的使用

项目地址&#xff1a;https://github.com/gzu-liyujiang/AndroidPicker 历史版本:https://github.com/gzu-liyujiang/AndroidPicker/blob/master/ChangeLog.md 依赖配置 // JitPack 远程仓库&#xff1a;https://jitpack.iomaven { url https://jitpack.io } 所有选择器的基…

网络套接字编程(三)

网络套接字编程(三) 文章目录 网络套接字编程(三)简易日志组件引入日志的原因日志等级打印日志函数将日志组件使用到服务端中 守护进程概念进程组、终端、会话守护进程的实现原理守护进程化组件将守护进程化组件使用到服务端中 补充知识关于inet_ntoa 在上一篇博客 网络套接字…

STM32:使用蓝牙模块

一、蓝牙概要 蓝牙是一种常见的无线通信协议&#xff0c;通常用于短距离通信。蓝牙分为经典蓝牙和低功耗蓝牙(BLE)。经典蓝牙通常用于需要持续传输数据的设备&#xff0c;比如蓝牙耳机等。低功耗蓝牙通常用于只需要间歇性传输数据的设备&#xff0c;比如运动手环。 蓝牙…

51单片机电子钟闹钟温度LCD1602液晶显示设计( proteus仿真+程序+原理图+设计报告+讲解视频)

51单片机电子钟闹钟温度液晶显示设计( proteus仿真程序原理图设计报告讲解视频&#xff09; 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接资料下载链接&#xff08;可点击&#xff09;&#xff1a; &#x1f31f;51单片…

【Redis】Java连接Redis及Java操作Redis常用数据类型

一&#xff0c;Java连接Redis 1.1 连接前端服务器 打开RedisDesktopManager并连接Redis 不知道可看我上一篇文章&#xff1a; 【Redis】安装(Linux&window)及Redis的常用命令-CSDN博客 1.2 后端依赖 导入相关的jedis依赖 注意&#xff1a;要在dependencies标签中导入…

【深度学习】pytorch——实现CIFAR-10数据集的分类

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 往期文章&#xff1a; 【深度学习】pytorch——快速入门 CIFAR-10分类 CIFAR-10简介CIFAR-10数据集分类实现步骤一、数据加载及预处理实现数据加载及预处理归一化的理解访问数据集Dataset对象Dataloader对象 二、…

【css3】涟漪动画

效果展示 dom代码 <div class"mapSelfTitle66"><div></div> </div> 样式代码 .mapSelfTitle66{width:120px;height:60px;position: relative;&>div{width:100%;height:100%;background: url("~/assets/images/video_show/err…

数据结构:邻接矩阵与邻接表

模型图 邻接矩阵 用于反应图中任意两点之间的关联&#xff0c;用二维数组表示比较方便 以行坐标为起点&#xff0c;列坐标为终点如果两个点之间有边&#xff0c;那么标记为绿色&#xff0c;如图&#xff1a; 适合表示稠密矩阵 邻接表 用一维数组 链表的形式表示&#xff…

离散数学实践(2)-编程实现关系性质的判断

*本文为博主本人校内的离散数学专业课的实践作业。由于实验步骤已经比较详细&#xff0c;故不再对该实验额外提供详解&#xff0c;本文仅提供填写的实验报告内容与代码部分&#xff0c;以供有需要的同学学习、参考。 -------------------------------------- 编程语言&#xff…

高效处理异常值的算法:One-class SVM模型的自动化方案

一、引言 数据清洗和异常值处理在数据分析和机器学习任务中扮演着关键的角色。清洗数据可以提高数据质量&#xff0c;消除噪声和错误&#xff0c;从而确保后续分析和建模的准确性和可靠性。而异常值则可能对数据分析结果产生严重影响&#xff0c;导致误导性的结论和决策。因此&…

人工智能与卫星:颠覆性技术融合开启太空新时代

人工智能与卫星&#xff1a;颠覆性技术融合开启太空新时代 摘要&#xff1a;本文将探讨人工智能与卫星技术的融合&#xff0c;并介绍其应用、发展和挑战。通过深入了解这一领域的前沿动态&#xff0c;我们将展望一个由智能卫星驱动的未来太空时代。 一、引言 近年来&#xf…

uniapp小程序砸金蛋抽奖

砸之前是金蛋png图片&#xff0c;点击砸完之后切换砸金蛋动效gif图片&#xff1b; 当前代码封装为砸金蛋的组件&#xff1b; vue代码&#xff1a; <template><view class"page" v-if"merchantInfo.cdn_static"><image class"bg&qu…

第6章_多表查询

文章目录 多表查询概述1 一个案例引发的多表连接1.1 案例说明1.2 笛卡尔积理解演示代码 2 多表查询分类讲解2.1 等值连接 & 非等值连接2.1.1 等值连接2.1.2 非等值连接 自连接 & 非自连接内连接与外连接演示代码 3 SQL99语法实现多表查询3.1 基本语法3.2 内连接&#x…

HTML脚本、字符实体、URL

HTML脚本&#xff1a; JavaScript 使 HTML 页面具有更强的动态和交互性。 <script> 标签用于定义客户端脚本&#xff0c;比如 JavaScript。<script> 元素既可包含脚本语句&#xff0c;也可通过 src 属性指向外部脚本文件。 JavaScript 最常用于图片操作、表单验…

【机器学习】几种常用的机器学习调参方法

在机器学习中&#xff0c;模型的性能往往受到模型的超参数、数据的质量、特征选择等因素影响。其中&#xff0c;模型的超参数调整是模型优化中最重要的环节之一。超参数&#xff08;Hyperparameters&#xff09;在机器学习算法中需要人为设定&#xff0c;它们不能直接从训练数据…

Locust:可能是一款最被低估的压测工具

01、Locust介绍 开源性能测试工具https://www.locust.io/&#xff0c;基于Python的性能压测工具&#xff0c;使用Python代码来定义用户行为&#xff0c;模拟百万计的并发用户访问。每个测试用户的行为由您定义&#xff0c;并且通过Web UI实时监控聚集过程。 压力发生器作为性…

Nacos报错Connection refused (Connection refused)(最后原因醉了,非常醉)

目录 一、问题产生二、排查思路1.nacos拒绝连接&#xff0c;排查思路&#xff1a;2.Nacos启动成功但是拒绝连接的几种原因&#xff1a; 三、实操过程&#xff08;着急解决问题直接看这个&#xff09;1.启动Nacos2.查看Nacos启动日志3.根据日志处理问题4.修改Nacos5.重启Nacos 一…

CSS基础知识点速览

1 基础认识 1.1 css的介绍 CSS:层叠样式表(Cascading style sheets) CSS作用&#xff1a; 给页面中的html标签设置样式 css写在style标签里&#xff0c;style标签一般在head标签里&#xff0c;位于head标签下。 <style>p{color: red;background-color: green;font-size…