第一步:创建项目目录
mkdir sqlalchemy_alembic
第二步:初始化数据库版本管理器
cd /xxx/xxx/sqlalchemy_alembic
# init后的名称随意取
alembic init alembic
第三步:创建数据库目录databases,用于保存数据库模型
-
创建目录
cd /xxx/xxx/sqlalchemy_alembic mkdir databases
-
创建模型,
models.py
内容如下from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.ext.declarative import declarative_base engine = create_engine("mysql+pymysql://admin:password@192.168.0.129/test?charset=utf8mb4") Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(64), nullable=False) password = Column(String(64), nullable=False) email = Column(String(64), nullable=False) def __repr__(self): return f"{self.__class__.__name__}(username: {self.username} | email: {self.email})"
-
目录树如下
F:\projects\sqlalchemy-alembic ├─almbic │ └─versions │ └─env.py │ └─script.py.mako │ └─README │─databases │ └─__init__.py │ └─models.py ├─__init__.py └─alembic.ini
第四步:修改生成的配置文件
-
alembic.ini
# 原文件 sqlalchemy.url = driver://user:pass@localhost/dbname # 修改后 sqlalchemy.url = mysql+pymysql://admin:password@192.168.0.129/test?charset=utf8mb4
-
alembic/env.py
import sys import os # os.path.dirname(__file__) 获取当前文件所在目录:alembic # os.path.abspath(os.path.dirname(__file__)) 获取当前目录绝对路径:F:\projects\sqlalchemy-alembic\almbic # os.path.pardir 返回上级目录:.. # 定位到到项目根目录 root_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), os.path.pardir) # 将项目路径添加进环境变量 sys.path.append(root_path) from databases import models # 原 target_metadata = None # 修改后 target_metadata = models.Base.metadata
第五步:初始化数据库,生成迁移文件
-
仅生成了迁移文件,未真正对数据库进行操作
alembic revision --autogenerate -m 'add table users'
-
类似于Django的以下命令
python3 manage.py makemigrations
-
该命令执行后会在
versions
下生成一个py文件,目录如下- 前面是版本唯一标识,后面的
_add_table_users
是备注信息
F:\projects\sqlalchemy-alembic ├─almbic │ └─versions │ │ └─65579825fb5d_add_table_users.py │ └─env.py │ └─script.py.mako │ └─README │─databases │ └─__init__.py │ └─models ├─__init__.py └─alembic.ini
- 前面是版本唯一标识,后面的
第六步:执行脚本迁移,生成数据表
-
由迁移脚本文件生成对应的数据表
alembic upgrade head
-
类似于Django的以下命令
python3 manage.py migrate
第七步:修改数据表模型,然后进行数据库迁移
-
第一步:修改模型
User
,新增real_name
字段real_name = Column(String(64), nullable=False, server_default='default')
server_default 和 default说明
server_default: 对于数据库原有数据,该列使用default默认值填充,新增的数据该列不能为空 server_default: 会修改表结构 server_default: 对于整型或者布尔型设置时需要特殊处理 from sqlalchemy import text is_del = Column(Boolean,server_default=text('False')) sit_num = Column(Integer,server_default=text('80')) default:对于数据库原有数据不做处理,新增的数据该列不能为空 default:不会修改表结构
-
第二步:生成迁移文件
alembic revision --autogenerate -m "add column realname with default"
-
第三步:进行数据库迁移
alembic upgrade head
迁移异常处理
-
一般生成迁移文件时不会出错
-
出错一般会在根据迁移文件映射到数据表时
-
出错后执行以下命令,然后根据报错修改相应内容
# 放弃本次迁移文件 alembic stamp head
-
修改后再次执行迁移命令,生成迁移文件
-
然后根据迁移文件,进行数据库映射
SQLALchemy外键
外键约束有以下几项:
-
RESTRICT
:若子表中有父表对应的关联数据,删除父表对应数据,会阻止删除。默认项 -
NO ACTION
:在MySQL中,同RESTRICT。 -
CASCADE
:级联删除。 -
SET NULL
:父表对应数据被删除,子表对应数据项会设置为NULL,前提是nullable=True
。
1. 一对一关系
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String(64), nullable=False)
password = Column(String(64), nullable=False)
def __repr__(self):
return f"{self.__class__.__name__}(username: {self.username} | group: {self.group})"
class UserInfo(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
phone = Column(String(64), nullable=False)
address = Column(String(64), nullable=False)
real_name = Column(String(64), nullable=False, server_default='default')
email = Column(String(64), nullable=False)
# 一对一外键关联,与一对多主要区别uselist
user_id = Column(Integer, ForeignKey('groups.id', ondelete='CASCADE'), nullable=False)
user = relationship('User', backref=backref("userinfo", uselist=False))
- 说明:
backhref(‘反向查找时的变量名:即user.userinfo可以获取到用户的详细信息’,uselist=False:指明该关系是一对一)
2.一对多关系
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String(64), nullable=False)
password = Column(String(64), nullable=False)
# 多对多外键关联
group_id = Column(Integer, ForeignKey('groups.id', ondelete='CASCADE'), nullable=False)
group = relationship('Group', backref='user')
def __repr__(self):
return f"{self.__class__.__name__}(username: {self.username} | group: {self.group})"
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
# 此处加了这个关联关系,就可以通过group对象.user获取组内的所有用户
# 此处不建议这样写,一般会定义在多表关系中的backhref属性中
# user = relationship(User)
def __repr__(self):
return f"{self.__class__.__name__}(name: {self.name})"
3.多对多关系
- 第一步:使用Table定义一个中间表,中间表一般就是包含两个模型的外键字段就可以了,并且让他们两个来作为一个“复合主键”
permission_group_mapping = Table(
"perms_group",
Base.metadata,
Column('permissions_id', Integer, ForeignKey('permissions.id'), primary_key=True),
Column('groups_id', Integer, ForeignKey('groups.id'), primary_key=True)
)
- 第二步:定义权限表
class Permission(Base):
__tablename__ = 'permissions'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
desc = Column(String(256), nullable=False)
def __repr__(self):
return f"{self.__class__.__name__}(name: {self.name})"
- 第三步:定义组表(关联关系可以写在二者中的任意一张表中)
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
# 关联关系
permissions = relationship("permissions", backref="groups", secondary=permission_group_mapping)
def __repr__(self):
return f"{self.__class__.__name__}(name: {self.name})"