实战:MyBatis适配多种数据库:MySQL、Oracle、PostGresql等

概叙

很多时候,一套代码要适配多种数据库,主流的三种库:MySQL、Oracle、PostGresql,刚好mybatis支持这种扩展,如下图所示,在一个“namespace”,判断唯一的标志是id+databaseId,刚好写了三个同样的方法,一个不带databaseId,两个带databaseId,此时当前库如果连接的是oracle则执行databaseId=’oracle‘的方法,如果连接的是MySQL则执行databaseId=’mysql‘的方法。不带databaseId的方法,只有在不写databaseId的方法且唯一只有一个不带databaseId方式时才会被执行。

不足:本文中mybatis虽然可以适配多种类型的数据库,但是启动后,只支持一种库;即要么连接oracle,要么连接MySQL,不能同时连接和操作两种库。

思考:如何改进不足,同时支持操作多种库?如果用原生的jdbc很好解决,但是我们用的是mybatis框架,如何让mybatis支持同时操作多种数据库?(动态数据源,其实和jdbc一样,系统启动时,就要初始化好多种库的连接,然后动态切换;具体实现,大家可以自行试试)

详细的我们接着往后看

一、启用数据库识别DatabaseIdProvider

1. 调查数据库产品名

要想做兼容多种数据库,那毫无疑问,我们首先得明确我们要兼容哪些数据库,他们的数据库产品名称是什么。得益于SPI设计,java语言制定了一个java.sql.DatabaseMetaData接口(jdbc接口),要求各个数据库的驱动都必须提供自己的产品名。因此我们如果想要兼容某数据库,只要在对应的驱动包中找到其对DatabaseMetaData的实现即可。

比如Mysql的驱动包 mysql-connector-java 下的 DatabaseMetaData

Oracle 的驱动包 com.oracle.ojdbc6 下的 OracleDatabaseMetaData

DatabaseMetaData接口是由JDBC驱动程序实现的,用于提供底层数据源相关的信息。该接口主要用于为应用程序或工具确定如何与底层数据源交互。应用程序也可以使用DatabaseMetaData接口提供的方法获取数据源信息。

DatabaseMetaData接口中包含超过150个方法,根据这些方法的类型可以分为以下几类:

(1)获取数据源信息。

(2)确定数据源是否支持某一特性或功能。

(3)获取数据源的限制。

(4)确定数据源包含哪些SQL对象以及这些对象的属性。

(5)获取数据源对事务的支持。

创建DatabaseMetaData对象

DatabaseMetaData对象的创建比较简单,需要依赖Connection对象。Connection对象中提供了一个getMetadata()方法,用于创建DatabaseMetaData对象。

一旦创建了DatabaseMetaData对象,我们就可以通过该对象动态地获取数据源相关的信息了。下面是创建DatabaseMetaData对象并使用该对象获取数据库表名允许的最大字符数的案例,代码如下:

Connection connection = DriverManager.getConnection("jdbc:mysql://XXXX/demo","XXXX","XXXX");
DatabaseMetaData dmd = connection.getMetaData();

获取数据源的基本信息

		//获取数据源的基本信息System.out.println("数据库URL:" + dmd.getURL());System.out.println("数据库用户名:" + dmd.getUserName());System.out.println("数据库产品名:" + dmd.getDatabaseProductName());System.out.println("数据库产品版本:" + dmd.getDatabaseProductVersion());System.out.println("驱动主版本:" + dmd.getDriverMajorVersion());System.out.println("驱动副版本:" + dmd.getDriverMinorVersion());System.out.println("数据库供应商用于schema的首选术语:" + dmd.getSchemaTerm());System.out.println("数据库供应商用于catalog的首选术语:" + dmd.getCatalogTerm());System.out.println("数据库供应商用于procedure的首选术语:" + dmd.getProcedureTerm());System.out.println("null值是否高排序:" + dmd.nullsAreSortedHigh());System.out.println("null值是否低排序:" + dmd.nullsAreSortedLow());System.out.println("数据库是否将表存储在本地文件中:" + dmd.usesLocalFiles());System.out.println("数据库是否为每个表使用一个文件:" + dmd.usesLocalFilePerTable());System.out.println("数据库SQL关键字:" + dmd.getSQLKeywords());

注意:由于HSQLDB驱动对DatabaseMetaData接口的getSQLKeywords()方法没有任何实现逻辑,只返回一个空字符串,因此上面的代码获取数据库SQL关键字内容为空。

获取数据源支持特性

DatabaseMetaData接口中提供了大量的方法用于确定数据源是否支持某个或一组特定的特性。除此之外,有些方法用于描述数据源对某一特性的支持级别。

获取数据源限制

获取SQL对象及属性

获取SQL对象及属性

2. 启用databaseId

既然各个驱动都提供了产品名,那么接下来就是让项目在启动中能够识别这些数据库,并赋予以不同数据库不同的id。MyBatis 其实有这项功能,但是这个功能默认没有被启用,若要启用我们首先得建立一个配置,即databaseIdProvider,可以在配置类里面加上这个Bean来实现

    @Beanpublic DatabaseIdProvider databaseIdProvider() throws SQLException {DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();Properties properties = new Properties();// Key值(即产品名)来源于数据库,需要提前查清楚 ,// value值(即databaseId)可以随便填,你填“1” "2" "3"也行,但建议有明确意义,像下面这样properties.setProperty("0racle", "oracle");properties.setProperty("MySQL", "mysql");properties.setProperty("DB2", "db2");properties.setProperty("Derby", "derby");properties.setProperty("H2", "h2");properties.setProperty("HSQL", "hsql");properties.setProperty("Informix", "informix");properties.setProperty("MS-SQL", "ms-sql");properties.setProperty("PostgresqL", "racle");properties.setProperty("sybase", "sybase");properties.setProperty("Hana", "hana");databaseIdProvider.setProperties(properties);return databaseIdProvider;}@Beanpublic SqlSessionFactory testdbSqlSessionFactory(@Qualifier("testdbDataSource") DataSource testdbDataSource)throws Exception {final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(testdbDataSource);//set 注入databaseIdProvider 到当前SqlSessionFactoryBean sessionFactory.setDatabaseIdProvider(databaseIdProvider()); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(TestDbDataSourceConfig.MAPPER_LOCATION));return sessionFactory.getObject();}

完成了上述配置后,我们的项目就能主动去识别数据库类型了。

二、SQL语法鉴别

对于大部分SQL,因为有SQL规范的限制,它们通常是通用的,一段SQL可以在不同的数据库上跑。但是对于部分复杂SQL,就得针对不同数据库,来写不同的SQL了,我们以Mysql 、 Oracle 为例,看一些常见功能的语法差异

1. 分页查询

MySQL中使用LIMIT关键字来实现分页查询,例如:

1

SELECT * FROM table_name LIMIT offset, count;

而Oracle中使用ROWNUM关键字来实现分页查询,例如:

1

2

3

4

5

SELECT *

FROM (SELECT t.*, ROWNUM AS rn

      FROM table_name t

      WHERE ROWNUM <= offset + count)

WHERE rn > offset;

2. 获取当前时间

MySQL中可以使用NOW()函数来获取当前时间,例如:

1

SELECT NOW();

而Oracle中可以使用SYSDATE关键字来获取当前时间,例如:

1

SELECT SYSDATE FROM DUAL;

3. 获取自增主键的值

MySQL中可以使用LAST_INSERT_ID()函数来获取最后插入行的自动生成的主键值,例如:

1

2

INSERT INTO table_name (column1, column2) VALUES(value1, value2);

SELECT LAST_INSERT_ID();

而Oracle中可以使用SEQUENCE和CURRVAL来获取自增主键的值,例如:

1

2

INSERT INTO table_name (column1, column2) VALUES(seq.nextval, value2);

SELECT seq.currval from dual;

4. 转换数据类型

MySQL 使用 CAST() 或 CONVERT() 函数转换数据类型,例如:

1

2

3

SELECT CAST('123' AS SIGNED) AS converted_value; 

-- 或者 

SELECT CONVERT('123', SIGNED) AS converted_value;

而Oracle使用 TO_NUMBER(), TO_CHAR(), TO_DATE() 等函数进行数据类型转换,例如:

1

2

INSERT INTO table_name (column1, column2) VALUES(seq.nextval, value2);

SELECT seq.currval from dual;

5. 字符串拼接

MySQL中可以使用CONCAT()函数来进行字符串拼接,例如:

1

SELECT CONCAT(column1, column2) FROM table_name;

而Oracle中可以使用||运算符来进行字符串拼接,例如:

1

SELECT column1 || column2 FROM table_name;

6. 字符串截取

MySQL 使用 SUBSTRING() 函数,例如:

1

SELECT SUBSTRING('Hello World', 1, 5) AS substring_result;

而Oracle 使用 SUBSTR() 函数,例如:

1

SELECT SUBSTR('Hello World', 1, 5) AS substring_result FROM DUAL;

7. 判空函数

MySQL中可以使用IFNULL()函数来进行字符串拼接,例如:

1

SELECT IFNULL(column1, "1") FROM table_name;

而Oracle中可以使用NVL()来进行字符串拼接,例如:

1

SELECT NVL(column1, "1") FROM table_name;

8. 正则表达式

MySQL 使用 REGEXP 或 RLIKE 进行正则表达式匹配,例如:

1

SELECT 'Hello World' REGEXP '^Hello' AS is_matched;

而Oracle 使用 REGEXP_LIKE, REGEXP_INSTR, REGEXP_SUBSTR, 和 REGEXP_REPLACE 等函数,例如:

1

SELECT CASE WHEN REGEXP_LIKE('Hello World', '^Hello') THEN 'Matched' ELSE 'Not Matched' END AS is_matched FROM DUAL;

9. 窗口函数

MySQL 低版本不支持窗口函数,可以使用自连接模拟窗口函数,例如:

1

2

3

4

5

SELECT t1.*

FROM table_name t1

LEFT JOIN table_name t2

ON t1.column_name = t2.column_name AND t1.order_column > t2.order_column

WHERE t2.column_name IS NULL;

而Oracle 或 MySQL高版本则可以 使用 窗口函数,例如:

1

2

3

4

SELECT *

FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY column_name ORDER BY order_column) as rn

      FROM table_name) as t

WHERE rn = 1;

三、SQL兼容处理

如果我们的项目有SQL语法不兼容的情况,如上面那些场景,那么我们就需要对这些SQL做特殊处理了,比如一个常用的功能,获取当前数据库时间。我们需要在同一个XML文件中写两份,注意两份SQL的 databaseId 是不同的,而不同数据库的 databaseId 是什么,则依赖我们最开始维护的databaseIdProvider 里的value值了

1

2

3

4

5

6

7

8

9

10

11

12

<select id = "getSysDateTime" databaseId="oracle">

    select

            TO_CHAR (sysdate, 'yyyyMMdd') sys_date,

            TO_CHAR (sysdate, 'HH24miss') sys_time

    from dual

</select>

<select id = "getSysDateTime" databaseId="mysql">

    select

            date_format (now(), '%Y%m%d') sys_date,

            date_format (now(), '%H%i%s') sys_time

    from dual

</select>

而一些可以跑在所有平台的SQL,则不需要改造,即databaseId不要填,如

1

2

3

4

<select id = "getUserInfo" resultType = "UserInfo">

    select user_name, user_age

    from USERINFO

</select>

四、运行原理

做完上述步骤后,我们的项目就能在多种数据库环境运行了,而其内部原理,其实也非常简答

1. 配置载入

在项目启动的时候,MyBatis 需要创建会话工厂,其中就有如下代码,他的意义很明确,就是找到当前连接的数据库,对应的是什么databaseId。并且将这个值保存进配置中。

1

2

3

4

5

6

7

8

9

10

11

12

// SqlSessionFactoryBean

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    // 省略无关代码

    if (this.databaseIdProvider != null) {

      try {

        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));

      } catch (SQLException e) {

        throw new NestedIOException("Failed getting a databaseId", e);

      }

    }

    // 省略无关代码

}

2. SQL选择

我们在 Mybatis之动态SQL使用小结(全网最新) 中介绍过MyBatis的启动流程,其中就有对xml文件的解析,而我们现在在一个xml中写了多个id相同的SQL,MyBatis会怎么做呢?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

// XMLMapperBuilder

  private void buildStatementFromContext(List<XNode> list) {

    // 如果当前环境有DatabaseId,则以这个DatabaseId去加载对应的SQL

    if (configuration.getDatabaseId() != null) {

      buildStatementFromContext(list, configuration.getDatabaseId());

    }

    // 兜底,把某些没有指明DatabaseId的SQL加载进来

    buildStatementFromContext(list, null);

  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {

    for (XNode context : list) {

      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);

      try {

        statementParser.parseStatementNode();

      } catch (IncompleteElementException e) {

        configuration.addIncompleteStatement(statementParser);

      }

    }

  }

可以看到对于一个XML文件的解析,会先后以指定databaseId 和无指定databaseId 两种情况去解析

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// XMLStatementBuilder

  public void parseStatementNode() {

    String id = context.getStringAttribute("id");

    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {

      return;

    }

    // 省略无关代码

}

  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {

    if (requiredDatabaseId != null) {

      return requiredDatabaseId.equals(databaseId);

    }

    if (databaseId != null) {

      return false;

    }

    id = builderAssistant.applyCurrentNamespace(id, false);

    if (!this.configuration.hasStatement(id, false)) {

      return true;

    }

    // skip this statement if there is a previous one with a not null databaseId

    MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2

    return previous.getDatabaseId() == null;

  }

可以看到,在读取每一段SQL块的时候,会判断SQL上标注的databaseId是否符合当前数据库环境,只有符合的才会被解析。

五、坑点

1. 避免歧义

不难发现,因为兜底逻辑的存在,有时可能会存在歧义,假设我们在mysql环境,我们写下这样的代码,是不是会把两段都解析掉?

1

2

3

4

5

6

7

8

9

10

11

12

<select id = "getSysDateTime" databaseId="mysql">

    select

            date_format (now(), '%Y%m%d') sys_date,

            date_format (now(), '%H%i%s') sys_time

    from dual

</select>

<select id = "getSysDateTime">

    select

            TO_CHAR (sysdate, 'yyyyMMdd') sys_date,

            TO_CHAR (sysdate, 'HH24miss') sys_time

    from dual

</select>

其实是不会的,因为在解析完后我们会把解析的结果存入一个map中,它的key值就是每一块的id,因为这个map是个内部定义的StrictMap,如下

在这里插入图片描述

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Override

@SuppressWarnings("unchecked")

public V put(String key, V value) {

  if (containsKey(key)) {

    throw new IllegalArgumentException(name + " already contains value for " + key

        + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));

  }

  if (key.contains(".")) {

    final String shortKey = getShortName(key);

    if (super.get(shortKey) == null) {

      super.put(shortKey, value);

    } else {

      super.put(shortKey, (V) new Ambiguity(shortKey));

    }

  }

  return super.put(key, value);

}

不难发现,一旦有两个id冲突(同一个命名空间下)直接就会报错,所以我们要知道,每一个id实际上只会被存储一次,我们应尽量避免出现歧义的写法

2. 复杂数据库场景

对于大部分场景,按照上面的做法就能解决,但是仍有部分场景是需要特殊处理的,比如同一个数据库的不同版本。

比如说都属于 MySQL 族,但是 MySQL 下又分 5.7 或 8.0,有些语法在低版本上不支持,又或者与Percona 和 Maria-db 等不兼容

此时就需要使用通用性SQL来写了,一般都是顺着低版本来写,但往往也是性能最差的写法。

Mybatis 类型映射

Mybatis 类型处理器映射关系图

这里列出一些默认的类型处理器处理JAVA与JDBC数据类型的映射关系图:

mybatis jdbcType与PostGreSQL数据类型对应表

mybatis jdbcType与Oracle mysql数据类型对应表

MybatisJdbcTypeOracleMySql
JdbcTypeARRAY
JdbcTypeBIGINTBIGINT
JdbcTypeBINARY
JdbcTypeBITBIT
JdbcTypeBLOBBLOBBLOB
JdbcTypeBOOLEAN
JdbcTypeCHARCHARCHAR
JdbcTypeCLOBCLOBCLOB
JdbcTypeCURSOR
JdbcTypeDATEDATEDATE
JdbcTypeDECIMALDECIMALDECIMAL
JdbcTypeDOUBLENUMBERDOUBLE
JdbcTypeFLOATFLOATFLOAT
JdbcTypeINTEGERINTEGERINTEGER
JdbcTypeLONGVARBINARY
JdbcTypeLONGVARCHARLONG VARCHAR
JdbcTypeNCHARNCHAR
JdbcTypeNCLOBNCLOB
JdbcTypeNULL
JdbcTypeNUMERICNUMERIC/NUMBERNUMERIC/
JdbcTypeNVARCHAR
JdbcTypeOTHER
JdbcTypeREALREALREAL
JdbcTypeSMALLINTSMALLINTSMALLINT
JdbcTypeSTRUCT
JdbcTypeTIMETIME
JdbcTypeTIMESTAMPTIMESTAMPTIMESTAMP
JdbcTypeTINYINTTINYINT
JdbcTypeUNDEFINED
JdbcTypeVARBINARY
JdbcTypeVARCHARVARCHARVARCHAR

mybatis的jdbcType中部分没有对应的oracle和mysql的数据类型中,后续碰到再具体分析。

更新日志

Mysql中没有CLOB类型

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

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

相关文章

mysql索引结构

多种数据结构 在数据库索引领域&#xff0c;特别是MySQL的InnoDB存储引擎中&#xff0c;聚簇索引&#xff08;Clustered Index&#xff09;和非聚簇索引&#xff08;也称为二级索引&#xff0c;Secondary Index&#xff09;是两种主要的索引类型。这些索引类型在数据结构的选择…

最优化原理(笔记)

内积是线性代数运算的一个结果&#xff0c;一行*一列。 内积的性质&#xff01; 什么是范数&#xff1f;&#xff1f;&#xff1f; 对称矩阵&#xff1a;关于主对角线对称&#xff01; 正定对称矩阵&#xff1a; 二阶导是正定的&#xff0c;f(x)就是严格的凸函数&#xff01;&a…

spring部分源码分析及Bean的生命周期理解

前言&#xff1a; 本文整体框架是通过refresh方法这个入口进入分析&#xff1a;分析IOC容器的创建及一些Bean的生命周期的知识点&#xff0c;写得确实一般般&#xff0c;感觉自己的有些前置知识并没有理解的很到位&#xff0c;所以&#xff0c;这篇文件先记录一下&#xff0c;…

推荐一款开箱即用、开源、免费的中后台管理系统模版

项目介绍 vue-pure-admin 是推荐一款开箱即用、开源&#xff08;遵循MIT License开源协议&#xff09;、免费的中后台管理系统模版&#xff0c;完全采用 ECMAScript 模块&#xff08;ESM&#xff09;规范来编写和组织代码&#xff0c;使用了最新的 Vue3、 Vite、Element-Plus、…

无人机图像目标检测技术详解

当前研究领域的热点之一。无人机搭载的高清摄像头能够实时捕获大量图像数据&#xff0c;对这些数据进行有效的目标检测对于军事侦察、环境监测、灾害救援等领域具有重要意义。本文将对无人机图像目标检测技术进行详解&#xff0c;包括图像处理技术、目标检测算法、关键技术应用…

pdf2docx - pdf 提取内容转 docx

文章目录 一、关于 pdf2docx主要功能限制 二、安装1、 PyPI2、从remote安装3、从源码安装4、卸载 三、转化 PDF例 1: convert all pages例 2: 转换指定页面例 3: multi-Processing例 4: 转换加密的pdf 四、提取表格五、命令行交互1、按页面范围2、按页码3、Multi-Processing 六…

gitee设置ssh公钥密码频繁密码验证

gitee中可以创建私有项目&#xff0c;但是在clone或者push都需要输入密码&#xff0c; 比较繁琐。 公钥则可以解决该问题&#xff0c;将私钥放在本地&#xff0c;公钥放在gitee上&#xff0c;当对项目进行操作时带有的私钥会在gitee和公钥进行验证&#xff0c;避免了手动输入密…

C语言数据结构课设:基于EasyX前端界面的飞机订票系统

数据结构课程设计说明书 学 院、系&#xff1a; 软件学院 专 业&#xff1a; 软件工程 班 级&#xff1a; 学 生 姓 名&#xff1a; 范 学 号&#xff1a; 设 计 题 目&#xff1a; 飞机订票系统 起 迄 日 期: 2024年6月18日~ 20…

【测试能力提升-AI】AI介绍

注释&#xff1a; 搞python的最终梦想&#xff0c;搞机器&#xff0c;玩深度&#xff0c;通网络&#xff0c;知模型&#xff0c;拿下AI技术&#xff0c;尽管只是测试&#xff0c;但是也是有梦想的 1. 目标 完成AI任务 ---- 掌握成熟、标准的任务解决方法掌握AI工具 ---- 完成…

2022 年中高职组“网络安全”赛项-海南省省竞赛任务书-1-B模块-B-4Web渗透测试

前言 本章节我将尝试操作B-4模块的渗透测试&#xff0c;搭建环境很难&#xff0c;还望大家点点赞多多支持&#xff01; 任务概览 最后4、5、6有一定的难度。 环境要求 kali Linux192.168.41.2Web服务器&#xff08;假设为PYsystem 2020 模拟平台&#xff09;192.168.41.7交换…

postman接口测试实战篇

击杀小游戏接口测试 接口测试简单介绍击杀小游戏代码下载单接口测试(postman)接口关联并参数化接口测试简单介绍 首先思考两个问题:1.接口是什么?2.接口测试是什么? 1.我们总是把接口想的很复杂,其实呢,它就是一个有特定输入和输出参数的交互逻辑处理单元,它不需要知…

【实战】Spring Cloud Stream3.0 整合RocketMq

文章目录 前言技术积累Spring Cloud Stream3.0新特性RocketMq简介 实战演示引入Maven依赖增加application配置消息生产者消息消费者 前言 相信很多同学用使用过rocketmq消息中间件&#xff0c;且大多情况下是使用原生的rocketmq-spring-boot-starter 进行集成然后创建一个rock…

Spring中Bean的循环依赖

目录 定义&#xff1a; 循环依赖的后果&#xff1a; 一&#xff1a;三级缓存 1、大概的思路&#xff1a; 注意&#xff1a; 2、执行过程&#xff1a; A半完成&#xff1a; B完成&#xff1a; A完成&#xff1a; 注&#xff1a; 二&#xff1a;Lazy 定义&#xff1a; …

入门C语言只需一个星期(星期三)

点击上方"蓝字"关注我们 01、基本数据类型 char 1 字节 −128 ~ 127 单个字符/字母/数字/ASCIIsigned char 1 字节 −128 ~ 127 -unsigned char 1 字节 0 ~ 255 -int…

【SpringCloud】微服务远程调用OpenFeign

工作原理流程图 上代码 common中添加依赖&#xff1a; <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency><groupId>org.spri…

CV13_混淆矩阵、F1分数和ROC曲线

1.1 混淆矩阵Confusion Matrix 混淆矩阵&#xff08;Confusion Matrix&#xff09;是机器学习和统计学中用于描述监督学习算法性能的特定表格布局。它是一种特定类型的误差矩阵&#xff0c;可以非常直观地表示分类模型在测试数据集上的预测结果与实际结果之间的对比。 混淆矩…

【数据结构】初识集合框架

&#x1f387;&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳&#xff0c;欢迎大佬指点&#xff01; 人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友…

Python爬虫(6) --深层爬取

深层爬取 在前面几篇的内容中&#xff0c;我们都是爬取网页表面的信息&#xff0c;这次我们通过表层内容&#xff0c;深度爬取内部数据。 接着按照之前的步骤&#xff0c;我们先访问表层页面&#xff1a; 指定url发送请求获取你想要的数据数据解析 我们试着将以下豆瓣读书页…

河南萌新联赛2024第(二)场:南阳理工学院

A 国际旅行Ⅰ D A*BBBB F 水灵灵的小学弟 H 狼狼的备忘录 I 重生之zbk要拿回属于他的一切 J 这是签到 ##A 国际旅行Ⅰ 链接&#xff1a;https://ac.nowcoder.com/acm/contest/87255/A 来源&#xff1a;牛客网 题目描述 很久很久以前&#xff0c;有 n n n 个国家&#xff0c;第…

字符的统计——423、657、551、696、467、535

423. 从英文中重建数字 最初思路 首先要有一个指针&#xff0c;对于3/4/5为一组地跳跃。起初想的是后瞻性&#xff0c;如果符合0-9任意&#xff0c;则更换index、跳跃。此时写了一个函数&#xff0c;用来判断s的截取段和0-9中有无符合。这个思路并没有进行下去&#xff0c;虽然…