1. 主键

在数据库中,主键都是设置成非空的。不过在SQLModel中,主键需要设置成可空的,如:

1
id: int | None = Field(default=None, primary_key=True)

这是因为这里的id 是由数据库自动生成的,而非在Python代码中。因此在创建一个实例的时候,一般都不会设置id,直到持久化到数据库中。因此,需要设置成可空字段,以免数据校验出错。

2. 连接数据库

可以通过create_engine创建一个数据库连接:

1
2
3
4
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

创建一个异步的数据库连接(需要安装aiosqlite包):

1
2
3
4
5
6
7
8
9
from sqlalchemy.ext.asyncio import create_async_engine
from sqlmodel.ext.asyncio.session import AsyncSession

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

engine = create_async_engine(sqlite_url)
async with AsyncSession(engine) as session:
# 数据库操作

3. 建表

定义完模型之后,可以使用SQLModel.metadata.create_all(engine)来创建数据库表,每一个继承自SQLModel并配置了table = True的类,都会在metadata属性中进行注册。因此,可以通过create_all创建所有表。

也正是这种注册关系,如果模型定义在另外的.py文件中时,需要在调用create_all前import已经定义好的模型。

注意:不建议在真实项目中使用create_all来创建数据库结构,SQLModel还没法很精确的控制类型、字段长度、字段顺序,复杂索引等问题,在需要进行数据库调优的场合中,建议同步维护对应的SQL文件,并保持模型定义的简洁性。

4. 数据插入

注意如下程序段:

1
2
3
4
5
6
with Session(engine) as session:  
session.add(hero_1)
print("Hero 1:", hero_1) # 输出 Hero 1: id=None name='Deadpond' secret_name='Dive Wilson' age=None
session.commit()
print("Hero 1:", hero_1) # 输出 Hero 1:
print("Hero 1 ID:", hero_1.id) # 输出 Hero 1 ID: 1
  • 第一个print,这个时候还未存入数据库,所以ID为None
  • 第二个print,已经提交到数据库,SQLAlchemy后台将该对象标定为过期。因为id,updatetime之类的数据可能在数据库中发生改变,因而目前内存中这个对象中的数据不是最新的。
  • 第三个print,这时需要该对象的一个属性的最新值,SQLAlchemy后台将会刷新载入该对象对应的数据库记录,在内存中形成最新数据版本。
  • 也可以用 session.refresh(object) 语句来显式刷新数据。

5. 数据查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 数据查询
with Session(engine) as session:
statement = select(Hero)
results = session.exec(statement)
for hero in results:
print(hero)

# 条件查询(下面只保留查询语句,省略session等处理)
statement = select(Hero).where(Hero.name == "Deadpond")
# 多个条件
statement = select(Hero).where(Hero.name == "Deadpond").where(Hero.age == 48)
# 不等于
statement = select(Hero).where(Hero.name != "Deadpond")
# 大于
statement = select(Hero).where(Hero.age > 35)
# 大于等于
statement = select(Hero).where(Hero.age >= 35)
# 介于
statement = select(Hero).where(Hero.age >= 35).where(Hero.age < 40)
# 也可以写成
statement = select(Hero).where(Hero.age >= 35, Hero.age < 40)
# 或关系
statement = select(Hero).where(or_(Hero.age <= 35, Hero.age > 90))

# 分页查询
statement = select(Hero).offset(3).limit(3)

# 主键查询,可能为None
hero = session.get(Hero, 1)

# 获取第一条数据,可能为None
hero = results.first()
# 获取一条数据,如果无数据或有多条数据,抛异常
hero = results.one()
# 获取所有数据
heroes = results.all()