前言
敬爱的读者们,在上一章节中,我们有针对性地为短链服务模块进行了初步的布局构想,其中涵盖了从数据存储结构到项目部署细节等诸多重要方面。在本篇文章中,我们将逐一落实上述设想并呈现具体实施步骤。在接下来的篇幅里,我们将着重探讨数据库及模型定义这两大基础要素的严谨配置过程。
应用配置信息
我是使用官方文档中推荐的方式:基于Pydantic和.env环境变量读取配置参数。
读取环境变量,需要安装python-doten包,如果你安装FastAPI是使用pip install fastapi[all]
这样的话会同时安装python-doten,否则的话需要执行pip install python-doten
来安装。
定义.env文件内容,项目简单,我目前先设置了两个变量,如下
ini
ASYNC_DATABASE_URI=sqlite+aiosqlite:////PycharmProjects/short_url/short.db
TOKEN_SIGN_SECRET=ZcjT6Rcp1yIFQoS7
然后,定义继承于BaseSettings模型的Settings子类,代码如下:
python
from typing import Optional
from pydantic.v1 import validator
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
ASYNC_DATABASE_URI: str
TOKEN_SIGN_SECRET:str
@validator('ASYNC_DATABASE_URI', pre=True)
def async_database_url_check(cls, v: str) -> Optional[str]:
if not v.startswith("sqlite+aiosqlite:///"):
return None
return v
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
@lru_cache()
def get_settings():
return Settings()
ASYNC_DATABASE_URI:连接异步引擎数据库的URL地址
TOKEN_SIGN_SECRET:TOEKN的签名信息值
Config
是 Settings
类的一个内部类,包含了两个类属性:env_file
和 env_file_encoding
。这些属性用于配置 Settings
类的行为,告诉它从名为 .env
的文件中加载配置,并指定文件编码为 UTF-8。
async_database_url_check
函数,引入Pydantic下的validator校验函数装饰器,对指定的ASYNC_DATABASE_URI字段执行特定校验,如果ASYNC_DATABASE_URI字段不是以sqlite+aiosqlite:///
开头就返回None,否则返回ASYNC_DATABASE_URI,我们使用异步方式,所以使用aiosqlite
最后,未来避免Settings对象被示例化多次,通过增加缓存的方式避免多次实例化,提高性能。
使用读取环境变量的方式,系统会自动解析当前的环境是否存在对应的值。如果环境变量值不存在且模型中定义的变量无默认值,则会触发校验异常;如果存在默认值,则该值就是模型中定义的默认值。
配置数据库引擎
在配置之前,我们先讲一下ORM。
ORM是对SQL语句的一种封装,它是对数据库表中列和行操作的一种对象映射。在某些情况下,使用ORM库时甚至不需要了解SQL,只需要通过对表映射出来的实体类模型进行操作就可实现对数据的增、删、改、查操作。 引入ORM库有以下几个好处:
- 对数据进行增、删、改、查更快捷。
- 有效避免编写SQL语句时以字符串拼接或字符串格式化的方式进行入参的传输,进而规避SQL注入问题。
- 可以自适应且高效地切换到其他DBMS(DataBase Management System),不需要额外修改逻辑,提高代码的可移植性和跨平台兼容性。
当然事物往往存在两面性,有好的一面,也会有坏的一面,在ORM库对SQL语句高度抽象封装的过程中会带来一些性能损失,所以需要根据自己的实际业务需求来权衡使用ORM的利弊。如果业务追求的是极致高性能,则建议自己编写SQL语句;如果要获得更加灵活、便捷、快速、高效的开发流程,那么建议选择ORM库。常见的ORM库主要有SQLAlchemy、Tortoise-orm、SQLModel、peewee、Database、piccolo、ormar。
这里我们选择非常知名的SQLAlchemy,支持异步方式
在数据库连接驱动库中,常见的同步库有PyMySQL、Redis、psycopg2、psycopg3、SQLite3等,对应的异步库主要有aiomysql、aiomongo、aioredis、asyncpg 、aiosqlite等。
当然,SQLAlchemy有同步和异步两种使用方式:
- 同步 :
create_engine
- 异步 :
create_async_engine
好了进入正题,在操作数据库的之前,需要有对应的引擎对象 和会话对象。在db目录下新建一个database.py文件。
ini
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import declarative_base, sessionmaker
from config.config import get_settings
async_engine = create_async_engine(get_settings().ASYNC_DATABASE_URI, echo=False)
Base = declarative_base()
SessionLocal = sessionmaker(bind=async_engine, expire_on_commit=False, class_=AsyncSession)
- async_engine:创建异步引擎对象
- Base:创建ORM模型基类。ORM是对数据库关系的对象映射,所以需要定义一个ORM模型类来对应数据库中的一个表,而模型类中定义的属性则对应表中列的数据。SQLAlchemy提供了一个ORM模型类的基类,这个基类通过declarative_base()函数返回,用户自定义的扩展模型类都需要基于这个基类来实现。
- SessionLocal:创建异步类型的数据库连接会话。使用sessionmaker类创建一个会话工厂(Session Factory),并调用其bind将engine绑定到该工厂上。接着通过工厂创建一个会话对象,并将其赋值给SessionLocal变量。此时,SessionLocal即可通过已绑定的引擎(async_engine)向数据库发起请求,执行相关操作。由sessionmaker创建出来的会话对象通常可以在模块级或全局范围内进行使用,因此它们可以同时被任意数量的函数和线程使用。
yield管理会话
建议可以先了解一下FastAPI依赖注入机制。
python
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from typing import AsyncGenerator
from db.database import SessionLocal
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
db_session = None
try:
db_session = SessionLocal()
yield db_session
finally:
await db_session.close()
定义了一个get_db_session()函数式的依赖项,在get_db_session()函数内部通过yield返回了一个db_session会话对象。这个db_session会话对象是一个AsyncGenerator异步生成器对象。 当把get_db_session()函数式依赖项注入视图函数后,此时,当有请求进来时,sessionLocal()函数会返回请求会话连接的引用。需要注意的是,sessionLocal()函数返回的是一个ThreadLocal对象,该对象在每个线程中都有自己的独立实例。由于当前是在协程函数中调用的,所以只有第一次调用db_session=sessionLocal()时才会创建一个新的数据库会话,并返回该会话的引用。在之后的调用中,则会返回同一个会话的引用。该会话连接对象在整个路由处理业务逻辑的过程中始终保持可用状态。当路由内的业务逻辑处理完成后(一般是返回响应报文后),会自动进入finally中执行await db_session.close(),也就是会话对象的关闭,以确保资源得到释放。使用这种方式对依赖项进行注入,可以更好地管理数据库会话连接对象的生命周期。
定义数据库表映射模型
完成上面的工作,就完成了基本配置,这里我们将进行表模型定义。
在model/model.py中编写,代码如下
ini
from db.database import Base
from sqlalchemy import Column, String, DateTime, func, Integer
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String(20))
password = Column(String(32))
created_at = Column(DateTime(), default=func.now())
class ShortUrl(Base):
__tablename__ = 'short_url'
id = Column(Integer, primary_key=True, autoincrement=True)
short_tag = Column(String(20),nullable=False)
short_url = Column(String(20))
long_url = Column(String, nullable=False)
visits_count= Column(Integer, nullable=True)
created_at = Column(DateTime(), default=func.now())
created_by = Column(String(20))
msg_context = Column(String, nullable=False)
- 通过
__tablename__
指定表名。 - 针对各个列属性,根据实际情况引入具体的数据类型,如Integer、String等。
- 设置默认的id列字段作为唯一标记记录,且将其设置为主键,另外通过autoincrement设置主键自增。
后续展望
在这篇文章中,我们主要介绍了数据库和模型定义的基础设置,并对部分知识点进行了简单的讲解。然而,由于主题较为复杂,有些知识点尚未深入探讨。如果您对此感兴趣,欢迎随时提出问题,会为您单独撰写文章进行解答。
敬请期待:下一篇文章将详细解读如何使用SQLAlchemy创建表以及用户信息CURD封装。在此,我们诚挚邀请各位朋友继续关注此系列文章,若有所得,恳请您给予点赞和收藏以示鼓励,不胜感激!