当前位置: 首页 > article >正文

三周精通FastAPI:27 使用使用SQLModel操作SQL (关系型) 数据库

官网文档:https://fastapi.tiangolo.com/zh/tutorial/sql-databases/

SQL (关系型) 数据库¶

FastAPI不需要你使用SQL(关系型)数据库。

但是您可以使用任何您想要的关系型数据库。

这里我们将看到一个使用SQLModel的示例。

SQLModel是在SQLAlchemy和Pydantic的基础上构建的。它是由FastAPI的同一作者制作的,与需要使用SQL数据库的FastAPI应用程序完美匹配。

小贴士

你可以使用任何其他你想要的SQL或NoSQL数据库库(在某些情况下称为“ORM”),FastAPI不会强迫你使用任何东西。

由于SQLModel基于SQLAlchemy,您可以轻松使用SQLAlchemi支持的任何数据库(这使得它们也受SQLModel支持)您可以很容易地将其调整为任何SQLAlchemy支持的数据库,如:

  • PostgreSQL
  • MySQL
  • SQLite
  • Oracle
  • Microsoft SQL Server,等等其它数据库

在此示例中,我们将使用SQLite,因为它使用单个文件并且 在Python中具有集成支持。因此,您可以复制此示例并按原样来运行它。

稍后,对于您的产品级别的应用程序,您可能会要使用像PostgreSQL这样的数据库服务器。

Tip

这儿有一个FastAPIPostgreSQL的官方项目生成器,全部基于Docker,包括前端和更多工具:https://github.com/tiangolo/full-stack-fastapi-postgresql

这是一个非常简单而简短的教程,如果你想了解数据库、SQL或更高级的功能,请参阅SQLModel文档。

安装SQLModel

首先,确保创建虚拟环境,激活它,然后安装sqlmodel:

pip install sqlmodel

Successfully installed SQLAlchemy-2.0.36 sqlmodel-0.0.22

使用单个模型创建应用程序

我们将首先使用单个SQLModel模型创建该应用程序最简单的第一个版本。

稍后,我们将通过以下多种型号来提高它的安全性和多功能性。🤓

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, Query
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)
    secret_name: str


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def get_session():
    with Session(engine) as session:
        yield session


SessionDep = Annotated[Session, Depends(get_session)]

app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/")
def create_hero(hero: Hero, session: SessionDep) -> Hero:
    session.add(hero)
    session.commit()
    session.refresh(hero)
    return hero


@app.get("/heroes/")
def read_heroes(
    session: SessionDep,
    offset: int = 0,
    limit: Annotated[int, Query(le=100)] = 100,
) -> list[Hero]:
    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
    return heroes


@app.get("/heroes/{hero_id}")
def read_hero(hero_id: int, session: SessionDep) -> Hero:
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero


@app.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    session.delete(hero)
    session.commit()
    return {"ok": True}

创建模型

导入SQLModel并创建数据库模型:

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, Query
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)
    secret_name: str

🤓 其他版本和变体

Hero类与Pydantic模型非常相似(事实上,在下面,它实际上是一个Pydantic模式)。

存在一些差异:

  • table=True告诉SQLModel这是一个表模型,它应该表示SQL数据库中的一个表,它不仅仅是一个数据模型(就像任何其他常规Pydantic类一样)。
  • 字段(primary_key=True)告诉SQLModel id是SQL数据库中的主键(您可以在SQLModel文档中了解有关SQL主键的更多信息)。
  • 通过将类型设置为int|None,SQLModel将知道该列在SQL数据库中应该是INTEGER,并且应该是NULLABLE。
  • 字段(index=True)告诉SQLModel,它应该为该列创建SQL索引,这样在读取由该列筛选的数据时可以更快地在数据库中查找。
  • SQLModel将知道声明为str的内容将是TEXT类型的SQL列(或VARCHAR,具体取决于数据库)。

创建引擎

SQLModel引擎(其下实际上是SQLAlchemy引擎)负责保存与数据库的连接。

您将有一个单一的引擎对象,用于所有代码连接到同一个数据库。

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, connect_args=connect_args)

使用check_same_thread=False允许FastAPI在不同线程中使用相同的SQLite数据库。这是必要的,因为一个请求可能会使用多个线程(例如在依赖关系中)。

别担心,根据代码的结构方式,我们将确保稍后每个请求使用一个SQLModel会话,这实际上是check_same_thread试图实现的。

创建表格

然后,我们添加一个函数,该函数使用SQLModel.media.create_all(engine)为所有表模型创建表。

def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

创建会话依赖关系

会话是将对象存储在内存中并跟踪数据中所需的任何更改,然后使用引擎与数据库通信。

我们将使用yield创建一个FastAPI依赖关系,为每个请求提供一个新的Session。这就是确保我们每个请求使用单个会话的原因。🤓

然后,我们创建一个带注释的依赖项SessionDep,以简化将使用此依赖项的其余代码。

def get_session():
    with Session(engine) as session:
        yield session


SessionDep = Annotated[Session, Depends(get_session)]

启动时创建数据库表

我们将在应用程序启动时创建数据库表。

app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()

在这里,我们在应用程序启动事件上创建表。

对于生产环境,您可能会使用在启动应用程序之前运行的迁移脚本。🤓

小贴士

SQLModel将有封装Alembic的迁移实用程序,但现在,您可以直接使用Alembic。

创建英雄库

因为每个SQLModel模型也是一个Pydantic模型,所以您可以在使用Pydantics模型的相同类型注释中使用它。

例如,如果你声明一个Hero类型的参数,它将从JSON正文中读取。

同样,您可以将其声明为函数的返回类型,然后数据的形状将显示在自动API文档UI中。

@app.post("/heroes/")
def create_hero(hero: Hero, session: SessionDep) -> Hero:
    session.add(hero)
    session.commit()
    session.refresh(hero)
    return hero

在这里,我们使用SessionDep依赖项(Session)将新的Hero添加到Session实例中,将更改提交到数据库中,刷新Hero中的数据,然后返回它。

读英雄库

我们可以使用select()从数据库中读取Heros。我们可以包含一个限制和偏移量来对结果进行分页。

@app.get("/heroes/")
def read_heroes(
    session: SessionDep,
    offset: int = 0,
    limit: Annotated[int, Query(le=100)] = 100,
) -> list[Hero]:
    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
    return heroes

读一个英雄条目

我们可以读一个英雄。

@app.get("/heroes/{hero_id}")
def read_hero(hero_id: int, session: SessionDep) -> Hero:
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero

如果不是英雄:

引发HTTPException(状态码=404,详细信息=“未找到英雄”)

删除英雄

我们也可以删除英雄。

@app.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    session.delete(hero)
    session.commit()
    return {"ok": True}

运行应用程序

您可以运行该应用程序:

fastapi-dev-main.py

然后转到/docs UI,您将看到FastAPI正在使用这些模型来记录API,它也将使用它们来序列化和验证数据。

使用多个模型更新应用程序

现在,让我们稍微重构一下这个应用程序,以提高安全性和多功能性。

如果你查看之前的应用程序,在UI中你可以看到,到目前为止,它让客户端决定要创建的英雄的id。😱

我们不应该让这种情况发生,他们可能会覆盖我们已经在数据库中分配的id。决定id应该由后端或数据库完成,而不是由客户端完成。

此外,我们为英雄创建了一个secret_name,但到目前为止,我们到处都在返回它,这不是什么秘密。。。😅

我们将通过添加一些额外的模型来解决这些问题。SQLModel将在这里大放异彩。✨

源代码:

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, Query
from sqlmodel import Field, Session, SQLModel, create_engine, select


class HeroBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)


class Hero(HeroBase, table=True):
    id: int | None = Field(default=None, primary_key=True)
    secret_name: str


class HeroPublic(HeroBase):
    id: int


class HeroCreate(HeroBase):
    secret_name: str


class HeroUpdate(HeroBase):
    name: str | None = None
    age: int | None = None
    secret_name: str | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def get_session():
    with Session(engine) as session:
        yield session


SessionDep = Annotated[Session, Depends(get_session)]
app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=HeroPublic)
def create_hero(hero: HeroCreate, session: SessionDep):
    db_hero = Hero.model_validate(hero)
    session.add(db_hero)
    session.commit()
    session.refresh(db_hero)
    return db_hero


@app.get("/heroes/", response_model=list[HeroPublic])
def read_heroes(
    session: SessionDep,
    offset: int = 0,
    limit: Annotated[int, Query(le=100)] = 100,
):
    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
    return heroes


@app.get("/heroes/{hero_id}", response_model=HeroPublic)
def read_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero


@app.patch("/heroes/{hero_id}", response_model=HeroPublic)
def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep):
    hero_db = session.get(Hero, hero_id)
    if not hero_db:
        raise HTTPException(status_code=404, detail="Hero not found")
    hero_data = hero.model_dump(exclude_unset=True)
    hero_db.sqlmodel_update(hero_data)
    session.add(hero_db)
    session.commit()
    session.refresh(hero_db)
    return hero_db


@app.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    session.delete(hero)
    session.commit()
    return {"ok": True}

创建多个模型

在SQLModel中,任何具有table=True的模型类都是表模型。
任何没有table=True的模型类都是数据模型,这些模型实际上只是Pydantic模型(带有一些小的额外功能)。🤓
使用SQLModel,我们可以使用继承来避免在所有情况下复制所有字段。


HeroBase-基类

让我们从一个HeroBase模型开始,该模型包含所有模型共享的所有字段:
名称
年龄

  • name
  • age
class HeroBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)

英雄-桌子模型


然后,让我们创建Hero,即实际的表模型,其中包含其他模型中并不总是包含的额外字段:
身份证件
秘密名称

  • id
  • secret_name

因为Hero继承自HeroBase,所以它也有在HeroBase中声明的字段,所以Hero的所有字段都是:
身份证件
名称
年龄
秘密名称

  • id
  • name
  • age
  • secret_name
class HeroBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)


class Hero(HeroBase, table=True):
    id: int | None = Field(default=None, primary_key=True)
    secret_name: str

HeroPublic-公共数据模型


接下来,我们创建一个HeroPublic模型,该模型将返回给API的客户端。
它具有与HeroBase相同的字段,因此不包括secret_name。
最后,我们英雄的身份得到了保护!🥷
它还重新声明id:int。通过这样做,我们与API客户端签订了合同,这样他们就可以总是期望id在那里并且是int(永远不会是None)。


小贴士
让返回模型确保一个值总是可用的,并且总是int(而不是None)对API客户端非常有用,他们可以编写更简单的具有这种确定性的代码。
此外,自动生成的客户端将具有更简单的接口,因此与您的API通信的开发人员可以更好地使用您的API。😎


HeroPublic中的所有字段都与HeroBase中的相同,id声明为int(不是None):
身份证件
名称
年龄
秘密名称

  • id
  • name
  • age
  • secret_name
class HeroBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)


class Hero(HeroBase, table=True):
    id: int | None = Field(default=None, primary_key=True)
    secret_name: str


class HeroPublic(HeroBase):
    id: int

HeroCreate-创建英雄的数据模型


现在我们创建一个HeroCreate模型,这个模型将验证来自客户端的数据。
它具有与HeroBase相同的字段,还具有secret_name。
现在,当客户端创建一个新英雄时,他们将发送secret_name,它将存储在数据库中,但这些秘密名称不会在API中返回给客户端。


小贴士
这就是你处理密码的方式。接收它们,但不要在API中返回它们。
您还可以在存储密码之前对其值进行哈希运算,切勿以纯文本形式存储。


HeroCreate的字段包括:
名称
年龄
秘密名称

  • name
  • age
  • secret_name
class HeroBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)


class Hero(HeroBase, table=True):
    id: int | None = Field(default=None, primary_key=True)
    secret_name: str


class HeroPublic(HeroBase):
    id: int


class HeroCreate(HeroBase):
    secret_name: str

HeroUpdate-更新英雄的数据模型


在之前的应用程序版本中,我们没有更新英雄的方法,但现在有了多个模型,我们可以做到。🎉
HeroUpdate数据模型有点特殊,它具有创建新英雄所需的所有相同字段,但所有字段都是可选的(它们都有一个默认值)。这样,当你更新英雄时,你可以只发送你想要更新的字段。
因为所有字段实际上都发生了变化(类型现在包括None,它们现在的默认值为None),我们需要重新声明它们。
我们真的不需要从HeroBase继承,因为我们正在重新声明所有字段。为了保持一致性,我会让它继承,但这不是必需的。这更多的是个人品味的问题。🤷
HeroUpdate的字段包括:


名称
年龄
秘密名称

  • name
  • age
  • secret_name
class HeroUpdate(HeroBase):
    name: str | None = None
    age: int | None = None
    secret_name: str | None = None


使用HeroCreate创建并返回一个HeroPublic


现在我们有了多个模型,我们可以更新应用程序中使用它们的部分。
我们在请求中接收HeroCreate数据模型,并从中创建Hero表模型。
这个新的表模型Hero将具有客户端发送的字段,并且还将具有数据库生成的id。
然后,我们返回与函数中相同的表模型Hero。但是,当我们使用HeroPublic数据模型声明response_model时,FastAPI将使用HeroPublic来验证和序列化数据。

@app.post("/heroes/", response_model=HeroPublic)
def create_hero(hero: HeroCreate, session: SessionDep):
    db_hero = Hero.model_validate(hero)
    session.add(db_hero)
    session.commit()
    session.refresh(db_hero)
    return db_hero


小贴士
现在我们使用response_model=HeroPublic而不是返回类型注释->HeroPublic,因为我们返回的值实际上不是HeroPublic。
如果我们声明了->HeroPublic,你的编辑和linter会抱怨(这是理所当然的)你返回的是Hero而不是HeroPublic。
通过在response_model中声明它,我们告诉FastAPI去做它的事情,而不会干扰类型注释以及编辑器和其他工具的帮助。


用HeroPublic阅读英雄


我们可以像以前一样读取Heros,同样,我们使用response_model=list[HeroPublic]来确保数据被正确验证和序列化。

@app.get("/heroes/", response_model=list[HeroPublic])
def read_heroes(
    session: SessionDep,
    offset: int = 0,
    limit: Annotated[int, Query(le=100)] = 100,
):
    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
    return heroes


与HeroPublic一起阅读《一个英雄》


我们可以读一个英雄:

@app.get("/heroes/{hero_id}", response_model=HeroPublic)
def read_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero


使用HeroUpdate更新英雄


我们可以更新英雄。为此,我们使用HTTP PATCH操作。
在代码中,我们得到一个包含客户端发送的所有数据的字典,只有客户端发送的数据,不包括任何仅作为默认值的值。为此,我们使用exclude_unset=True。这是主要的伎俩。🪄
然后,我们使用hero_db.sqlmodel_update(hero_data)用hero_da中的数据更新hero_db。

@app.patch("/heroes/{hero_id}", response_model=HeroPublic)
def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep):
    hero_db = session.get(Hero, hero_id)
    if not hero_db:
        raise HTTPException(status_code=404, detail="Hero not found")
    hero_data = hero.model_dump(exclude_unset=True)
    hero_db.sqlmodel_update(hero_data)
    session.add(hero_db)
    session.commit()
    session.refresh(hero_db)
    return hero_db


再次删除英雄


删除英雄几乎是一样的。
我们不会满足在这个项目中重构所有内容的愿望。😅

@app.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    session.delete(hero)
    session.commit()
    return {"ok": True}


再次运行应用程序


您可以再次运行该应用程序:

fastapi dev main.py


输出信息:Uvicorn正在运行http://127.0.0.1:8000(按CTRL+C退出)



如果你转到/docs API UI,你会看到它现在已经更新,并且它不会期望在创建英雄时从客户端接收id,等等。


回顾


您可以使用SQLModel与SQL数据库交互,并使用数据模型和表模型简化代码。
您可以在SQLModel文档中了解更多信息,其中有一个关于使用SQLModel和FastAPI的较长迷你教程。🚀

实践

 安装SQLModel

首先,确保创建虚拟环境,激活它,然后安装sqlmodel:

pip install sqlmodel

源代码

存储文件到sql.py

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, Query
from sqlmodel import Field, Session, SQLModel, create_engine, select


class HeroBase(SQLModel):
    name: str = Field(index=True)
    age: int | None = Field(default=None, index=True)


class Hero(HeroBase, table=True):
    id: int | None = Field(default=None, primary_key=True)
    secret_name: str


class HeroPublic(HeroBase):
    id: int


class HeroCreate(HeroBase):
    secret_name: str


class HeroUpdate(HeroBase):
    name: str | None = None
    age: int | None = None
    secret_name: str | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

connect_args = {"check_same_thread": False}
engine = create_engine(sqlite_url, connect_args=connect_args)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def get_session():
    with Session(engine) as session:
        yield session


SessionDep = Annotated[Session, Depends(get_session)]
app = FastAPI()


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.post("/heroes/", response_model=HeroPublic)
def create_hero(hero: HeroCreate, session: SessionDep):
    db_hero = Hero.model_validate(hero)
    session.add(db_hero)
    session.commit()
    session.refresh(db_hero)
    return db_hero


@app.get("/heroes/", response_model=list[HeroPublic])
def read_heroes(
    session: SessionDep,
    offset: int = 0,
    limit: Annotated[int, Query(le=100)] = 100,
):
    heroes = session.exec(select(Hero).offset(offset).limit(limit)).all()
    return heroes


@app.get("/heroes/{hero_id}", response_model=HeroPublic)
def read_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    return hero


@app.patch("/heroes/{hero_id}", response_model=HeroPublic)
def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep):
    hero_db = session.get(Hero, hero_id)
    if not hero_db:
        raise HTTPException(status_code=404, detail="Hero not found")
    hero_data = hero.model_dump(exclude_unset=True)
    hero_db.sqlmodel_update(hero_data)
    session.add(hero_db)
    session.commit()
    session.refresh(hero_db)
    return hero_db


@app.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int, session: SessionDep):
    hero = session.get(Hero, hero_id)
    if not hero:
        raise HTTPException(status_code=404, detail="Hero not found")
    session.delete(hero)
    session.commit()
    return {"ok": True}

启动服务

执行命令:

fastapi dev sql.py

执行后显示:

INFO     Importing from /Users/skywalk/work/fastapi                
                                                                   
 ╭─ Python module file ─╮                                          
 │                      │                                          
 │  🐍 sql.py           │                                          
 │                      │                                          
 ╰──────────────────────╯                                          
                                                                   
INFO     Importing module sql                                      
INFO     Found importable FastAPI app                              
                                                                   
 ╭─ Importable FastAPI app ─╮                                      
 │                          │                                      
 │  from sql import app     │                                      
 │                          │                                      
 ╰──────────────────────────╯                                      
                                                                   
INFO     Using import string sql:app                               
                                                                   
 ╭────────── FastAPI CLI - Development mode ───────────╮           
 │                                                     │           
 │  Serving at: http://127.0.0.1:8000                  │           
 │                                                     │           
 │  API docs: http://127.0.0.1:8000/docs               │           
 │                                                     │           
 │  Running in development mode, for production use:   │           
 │                                                     │           
 │  fastapi run                                        │           
 │                                                     │           
 ╰─────────────────────────────────────────────────────╯           
                                                                   
INFO:     Will watch for changes in these directories: ['/Users/xxx/work/fastapi']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [37935] using WatchFiles
INFO:     Started server process [37941]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

测试

浏览docs页面:

执行curl添加指令

curl -X 'POST' \
  'http://127.0.0.1:8000/heroes/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "string",
  "age": 0,
  "secret_name": "string"
}'

 输出:

{"name":"string","age":0,"id":2}

证明一条信息被添加

查看一下:

curl http://127.0.0.1:8000/heroes/
[{"name":"string","age":0,"id":1},{"name":"string","age":0,"id":2}]

果然多了一条信息。


http://www.kler.cn/a/382355.html

相关文章:

  • Imperva 数据库与安全解决方案
  • nVisual标签打印模块的部署与使用
  • SpringBoot基础系列学习(二):日志
  • SQL常见语法
  • 产品经理的重要性
  • HarmonyOS使用arkTS拉起指定第三方应用程序
  • 视图-数据库sqlserver
  • jmeter 性能测试步骤是什么?
  • 代码随想录训练营Day18 | 77. 组合 - 216.组合总和III - 17.电话号码的字母组合
  • Qml组件之Text
  • DGL库之dgl.function.u_mul_e(代替dgl.function.src_mul_edge)
  • 模拟实现strcat函数
  • 线程池核心参数有哪些
  • Vue 组件传递数据-Props(六)
  • Vue+Springboot 前后端分离项目如何部署?
  • 【FPGA】Verilog:理解德摩根第一定律: ( ̅A + ̅B) = ̅A x ̅B
  • 爬虫下载网页文夹
  • 【C++刷题】力扣-#697-数组的度
  • 【人工智能】Transformers之Pipeline(二十二):零样本文本分类(zero-shot-classification)
  • 7.2 设计模式
  • [WSL][桌面][X11]WSL2 Ubuntu22.04 安装Ubuntu桌面并且实现GUI转发(Gnome)
  • 【论文阅读】-- 多元时间序列聚类算法综述
  • Sigrity Power SI 3D-EM Full Wave Extraction模式如何进行S参数提取和观测3D电磁场和远场操作指导(一)
  • “再探构造函数”(2)
  • 解释器模式:有效处理语言的设计模式
  • Redis 权限控制(ACL)|ACL 命令详解、ACL 持久化