实战: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来实现

    @Bean
    public 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;
    }

    @Bean
    public 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数据类型对应表

Mybatis

JdbcType

Oracle

MySql

JdbcType

ARRAY

JdbcType

BIGINT

BIGINT

JdbcType

BINARY

JdbcType

BIT

BIT

JdbcType

BLOB

BLOB

BLOB

JdbcType

BOOLEAN

JdbcType

CHAR

CHAR

CHAR

JdbcType

CLOB

CLOB

CLOB

JdbcType

CURSOR

JdbcType

DATE

DATE

DATE

JdbcType

DECIMAL

DECIMAL

DECIMAL

JdbcType

DOUBLE

NUMBER

DOUBLE

JdbcType

FLOAT

FLOAT

FLOAT

JdbcType

INTEGER

INTEGER

INTEGER

JdbcType

LONGVARBINARY

JdbcType

LONGVARCHAR

LONG VARCHAR

JdbcType

NCHAR

NCHAR

JdbcType

NCLOB

NCLOB

JdbcType

NULL

JdbcType

NUMERIC

NUMERIC/NUMBER

NUMERIC/

JdbcType

NVARCHAR

JdbcType

OTHER

JdbcType

REAL

REAL

REAL

JdbcType

SMALLINT

SMALLINT

SMALLINT

JdbcType

STRUCT

JdbcType

TIME

TIME

JdbcType

TIMESTAMP

TIMESTAMP

TIMESTAMP

JdbcType

TINYINT

TINYINT

JdbcType

UNDEFINED

JdbcType

VARBINARY

JdbcType

VARCHAR

VARCHAR

VARCHAR

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

更新日志

Mysql中没有CLOB类型

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

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

相关文章

电子信息工程自动化 单片机彩灯控制

摘要 随着社会经济和科学技术的不断进步&#xff0c;人们在保持发展的同时&#xff0c;环境带给人类的影响已经不足以让我们忽视&#xff0c;所以城市的美化问题慢慢的进入了人们的眼帘&#xff0c;PLC的产生给带电子产品带来了巨大变革&#xff0c;彩灯的使用在城市的美化中变…

【后台管理系统】-【组件封装】

目录 组件封装搜索组件table列表组件content组件自定义插槽定制modal组件动态获取options数据 组件封装 搜索组件 给页面写一个配置文件&#xff0c;将配置文件传入组件&#xff0c;可直接生成页面&#xff0c;以下面页面为例&#xff0c; 新建src/views/main/system/depart…

「嵌入式系统设计与实现」书评:学习一个STM32的案例

本文最早发表于电子发烧友论坛&#xff1a;【新提醒】【「嵌入式系统设计与实现」阅读体验】 学习一个STM32的案例 - 发烧友官方/活动 - 电子技术论坛 - 广受欢迎的专业电子论坛!https://bbs.elecfans.com/jishu_2467617_1_1.html 感谢电子发烧友论坛和电子工业出版社的赠书。 …

设计模式:20、状态模式(状态对象)

目录 0、定义 1、状态模式的三种角色 2、状态模式的UML类图 3、示例代码 0、定义 允许一个对象在其内部状态改变时改变它的行为&#xff0c;对象看起来似乎修改了它的类。 1、状态模式的三种角色 环境&#xff08;Context&#xff09;&#xff1a;环境是一个类&#xff0…

idea中新建一个空项目

目的&#xff0c;为了在同一个目录下有多个小的项目&#xff1a;使用IDE为idea2022。 步骤&#xff1a; 点击新建项目&#xff0c;点击创建空项目&#xff0c;这里选择空项目是将其作为其他项目的一个容器&#xff0c;如图所示&#xff1a; 然后点击文件->项目结构&#xf…

Java基础复习

“任何时候我也不会满足&#xff0c;越是多读书&#xff0c;就越是深刻地感到不满足&#xff0c;越感到自己知识贫乏。科学是奥妙无穷的。” ——马克思 目录 一、方法&方法重载 二、运算符 三、数据类型 四、面向对象 1. 面向对象思想 2. 引用传递 3. 访问权限修饰…

嵌入式里的“移植”概念

这里因为最近一年看到公司某项目很多代码上有直接硬件的操作&#xff0c;这里有感而发&#xff0c;介绍移植的概念。 一、硬件 先上一个图&#xff1a; 举个例子&#xff0c;大学里应该都买过开发板&#xff0c;例如st的&#xff0c;这里三个层次&#xff0c; 内核&#xff…

量子计算与商业转型之旅

近年来&#xff0c;各组织对量子计算的应用有所增加&#xff0c;并使全球商业运营发生了显著变化。《财富商业洞察》的一份报告显示&#xff0c;2022 年量子计算市场价值为 7.17 亿美元&#xff0c;预计到 2030 年将达到 65.28 亿美元。 从本质上讲&#xff0c;量子计算机与经…

周末和男朋友户外运动美好时光

周末的时光总是那么令人期待&#xff0c;仿佛一周的忙碌和疲惫都在这一刻得到了释放。这次&#xff0c;我和男朋友决定到户外去锻炼身体&#xff0c;享受一下大自然的馈赠。 清晨的阳光透过窗帘的缝隙洒进房间&#xff0c;我懒洋洋地睁开眼睛&#xff0c;看到男朋友已经在一旁整…

【Git】:标签管理

目录 理解标签 创建标签 操作标签 理解标签 标签的作用 标记版本&#xff1a;标签 tag &#xff0c;可以简单的理解为是对某次 commit 的⼀个标识&#xff0c;相当于起了⼀个别名。例如&#xff0c;在项目发布某个版本的时候&#xff0c;针对最后⼀次 commit 起⼀个 v1.0 这样…

QT的ui界面显示不全问题(适应高分辨率屏幕)

//自动适应高分辨率 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);一、问题 电脑分辨率高&#xff0c;默认情况下&#xff0c;打开QT的ui界面&#xff0c;显示不全按钮内容 二、解决方案 如果自己的电脑分辨率较高&#xff0c;可以尝试以下方案&#xff1a;自…

【Elasticsearch】初始化默认字段及分词

1、添加分词插件 1&#xff09;在线安装 执行命令 需要指定相同的版本 bin/elasticsearch-plugin.bat install https://get.infini.cloud/elasticsearch/analysis-ik/7.17.24 2&#xff09;离线安装 将安装包解压到 /plugins 目录下 安装包可以从对应的资源处下载 启动成…

MATLAB直流电机模型,直流电机控制

直流电机控制简介 直流电机&#xff08;DC motor&#xff09;广泛应用于各种机械驱动和电力控制系统中&#xff0c;其运行性能的控制至关重要。为了精准地控制直流电机的输出特性&#xff0c;可以通过不同的控制方式进行调节。常见的控制方式包括电枢电流控制、速度控制、电机位…

Linux之封装线程库和线程的互斥

Linux之封装线程库和线程的互斥与同步 一.封装线程库二.线程的互斥2.1互斥量的概念2.2初始化和销毁互斥量2.3加锁和解锁2.4互斥量的原理2.5可重入和线程安全2.6死锁 一.封装线程库 其实在我们C内部也有一个线程库而C中的线程库也是封装的原生线程库的函数&#xff0c;所以我们…

PHP语法学习(第九天)—PHP连接mysql详解(下)

首先&#xff0c;温馨提示&#xff0c;该部分内容跟昨天“PHP语法学习(第八天)—PHP连接mysql详解(上)”一起食用更佳噢&#xff01;&#xff01; 学习本篇内容必须掌握数据库基础命令点击“MYSQL 数据库”~~ 本文是接着PHP连接mysql的知识点接着讲&#xff0c;今天主要讲述PHP…

qt基本部分控件用法(一)

前言: 以前 windows下做工具主要是MFC&#xff0c;趁有点空时间&#xff0c;研究了QT&#xff0c;感觉跟MFC 差不多&#xff0c;VS 比 QT CREATOR 还是强大&#xff0c;不过QT可以跨平台&#xff0c;功能更强大&#xff0c;MFC 只能在win平台下.&#xff1b; 1&#xff1a;环境…

Mysql索引,聚簇索引,非聚簇索引,回表查询

什么是索引 数据库索引是为了实现高效数据查询的一种有序的数据数据结构&#xff0c;类似于书的目录&#xff0c;通过目录可以快速的定位到想要的数据&#xff0c;因为一张表中的数据会有很多&#xff0c;如果直接去表中检索数据效率会很低&#xff0c;所以需要为表中的数据建立…

以MP6924A为核心的LLC拓扑学习【一】

PFCLLC: 在PFC&#xff08;功率因数校正&#xff09;和LLC&#xff08;谐振变换器&#xff09;组成的电源系统中&#xff0c;各个电路有特定的作用&#xff0c;它们协同工作以实现高效率和高功率因数的电能转换。 1. PFC&#xff08;功率因数校正&#xff09;电路的作用 PFC电…

️ 在 Windows WSL 上部署 Ollama 和大语言模型的完整指南20241206

&#x1f6e0;️ 在 Windows WSL 上部署 Ollama 和大语言模型的完整指南 &#x1f4dd; 引言 随着大语言模型&#xff08;LLM&#xff09;和人工智能的飞速发展&#xff0c;越来越多的开发者尝试在本地环境中部署大模型进行实验。然而&#xff0c;由于资源需求高、网络限制多…

1-1 ESP32开发环境配置

前言&#xff1a; 基于Arduio配置ESP32开发环境... 目录 前言&#xff1a; 1.0 安装Python 2.0 安装VSCode 3.0 VSCode实用插件 4.0 替换VSCode配置&#xff08;可选&#xff09; 后记 1.0 安装Python 在windows操作系统的搜索框中搜索Microsoft Store 点击获取 安装完成…