orm 框架 之 sqlalchemy

一. sqlalchemy 简介

1 下载

pip3 install sqlalchemy

更多: http://docs.sqlalchemy.org/en/latest/dialects/index.html

2 组成部分

Engine --->>> 框架的引擎

Connection Pooling --->>> 数据库连接池

Dialect --->>> 选择连接数据库的DB API种类:mysql,sqllite。。。

Schema/Types --->>> 架构和类型

SQL Exprression Language --->>> SQL表达式语言

3 能够操作的关系型数据库

1. pymysql
   # username: 数据库用户名, password: 数据库密码, host:地址(127.0.0.1:3306), dbname: 数据库名称
   mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]

2. MySQL-Connector
   mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>

3. cx_Oracle
   oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]


4 原生操作使用

1. 导入包
from threading import Thread
from sqlalchemy import create_engine

2. 实例化得到一个 engine 对象, create_engine 是一个类。
engine = create_engine(
    # 连接数据库
    'mysql+pymysql://root:455525@127.0.0.1:3306/cnblogs?charset=utf8',
    # 超过连接池大小外最多创建的连接
    max_overflow=0,
    # 连接池大小
    pool_size=5,
    # 池中没有,线程最多等待的时间,否则报错
    pool_timeout=30,
    # 多久之后对线程池中的线程进行一次连接的回收(重置)
    pool_recycle=-1
)

3. 通过engine拿到一个链接, 拿到一个conn对象,从连接池中取出一个链接
def task():
    conn = engine.raw_connection()
    cursor = conn.cursor()
    cursor.execute("select id from article")
    res = cursor.fetchall()
    print(res)

4. 多线程测试
for i in range(5):
    t = Thread(target=task)
    t.start()

二. 创建操作表 >>> models.py

1. 可以以脚本的形式运行 >>> 右键点击启动即可

2. 可以以命令的形式运行 >>> python models.py init_db

3. ★★不要忘记修改 create_all( ) 和 drop_all( )

4. 只能全部创建 或 全部删除, 不能操作几个字段 或 表

1 新版本

建立字段如下,连接数据库等操作可参考老版本。

from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user_account"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    fullname: Mapped[Optional[str]]
    addresses: Mapped[List["Address"]] = relationship(
        back_populates="user", cascade="all, delete-orphan"
    )
    def __repr__(self) -> str:
        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

class Address(Base):
    __tablename__ = "address"
    id: Mapped[int] = mapped_column(primary_key=True)
    email_address: Mapped[str]
    user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
    user: Mapped["User"] = relationship(back_populates="addresses")
    def __repr__(self) -> str:
        return f"Address(id={self.id!r}, email_address={self.email_address!r})"

2 老版本

# 导入相应的依赖
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, DateTime, Text, UniqueConstraint, Index, create_engine
from flask_script import Manager
from flask import Flask
import datetime

app = Flask(__name__)

# 创建成一个Base
Base = declarative_base()
# 利用自定义命令的形式执行文件
manage = Manager(app)


class User(Base):
    id = Column(Integer, primary_key=True)  # id 主键
    name = Column(String(32), index=True, nullable=False)
    email = Column(String(32), unique=True)  # 唯一
    ctime = Column(DateTime, default=datetime.datetime.now)
    extra = Column(Text, nullable=True)

    # 自定义数据库表名称
    __tablename__ = 'users'

    __table_args__ = (
        # id 和 name 联合唯一
        UniqueConstraint('id', 'name', name='uix_id_name'),
        # 联合索引
        Index('ix_id_name', 'name', 'email'),
    )


class Book(Base):
    __tablename__ = 'books'  # 数据库表名称
    id = Column(Integer, primary_key=True)  # id 主键
    name = Column(String(32), index=True, nullable=False)  # varchar32  name列,索引,不可为空
    price = Column(Integer)


class Publish(Base):
    __tablename__ = 'publish'  # 数据库表名称
    id = Column(Integer, primary_key=True)  # id 主键
    name = Column(String(32), nullable=True)


@manage.command
def init_db():
    engine = create_engine(
        "mysql+pymysql://root:455525@127.0.0.1:3306/aaa?charset=utf8",
        max_overflow=0,  # 超过连接池大小外最多创建的连接
        pool_size=5,  # 连接池大小
        pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
        pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
    )

    # 创建出所有被 Base 管理的表
    Base.metadata.create_all(engine)

    # 删除所有被 Base 管理的表
    Base.metadata.drop_all(engine)


if __name__ == '__main__':
    manage.run()

三. 快速插入数据 >>> 没有做到线程安全

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import User, Book

# 第一步:创建engine
engine = create_engine("mysql+pymysql://root:455525@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)

# 第二步:通过engine,获得session对象:跟之前学的cookie,session不是一个东西。此 session: 会话。
Session = sessionmaker(bind=engine)
# 每次执行数据库操作时,都需要创建一个Connection
session = Session()

# 第三步,通过session操作插入数据
# book=Book(name='不良人',price=33)
user = User(name='pyy', email='123@qq.com', extra='有品')
session.add(user)
session.commit()
session.close()

四. scoped_session >>> 做到了线程安全

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import User, Book

# 第一步:创建engine
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)

# 第二步:通过engine,获得session对象:跟之前学的cookie,session不是一个东西
Session = sessionmaker(bind=engine)


# session是链接对象,如果集成到flask中,我们是吧session定义成全局,还是每个视图函数一个session呢?正常来讲要每个视图函数定义一个session,有些麻烦

# sqlalchemy 帮咱提供了一个只要定义一次的session,能够做到在不同线程中,使用的是自己的session,底层基于local

from sqlalchemy.orm import scoped_session
from threading import Thread

# session=Session() #没有线程安全

# 以后咱们使用这个它做到了线程安全
session = scoped_session(Session)
def task(i):
    user = User(name='pyy%s' % i, email='%s@qq.com' % i, extra='有品')
    session.add(user)
    session.commit()
    session.close()

for i in range(50):
    t = Thread(target=task, args=[i, ])
    t.start()

五. 增删改查

1 基本操作

1.1. 初始化

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Boy, Girl, Boy2Girl, Hobby, Book, User, Person
from sqlalchemy.orm import scoped_session
from sqlalchemy.sql import text

engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)
Session = sessionmaker(bind=engine)
session = scoped_session(Session)

"""
   这里做增删改查的操作
"""

# 提交数据
session.commit()
# 并关闭此次连接。 相当于数据操作完后将连接放回连接池中, 不是真正的关闭。
session.close()

1.2. 增加数据

1. 增加单条数据 >>> add()

    user01 = User(name='lyf', email='11111@qq.com', extra='有品')
    session.add(user01)

2. 增加多条数据 >>> add_all()

    book01 = Book(name='斗破苍穹', price=66)
    book02 = Book(name='不良人', price=88)
    publish01 = Publish(name='上海出版社')
    session.add_all([book01, book02, publish01])

1.3. 删除数据

# 返回值是删除的个数
    res = session.query(Book).filter_by(price=11).delete()

1.4. 修改数据

1. 普通修改
    res = session.query(Book).filter(Book.id < 4).update({"name": "修仙"})

2. 字符串相加
    # update({Book.name: Book.name + "番外"}  == update({"name": Book.name + "番外"}
    res = session.query(Book).filter(Book.id > 0).update({Book.name: Book.name + "番外"}, synchronize_session=False)

3. 数字相加
    res = session.query(Book).filter(Book.price > 20).update({"price": Book.price - 1}, synchronize_session="evaluate")

1.5. 查询数据

查询之后是一个 list 对象, 可以直接对其遍历

基本操作

1. 查询所有
    res = session.query(User).all()

2. 查询某些字段 >>> label: 别名
    res = session.query(Book.name.label('xx'), Book.id)

3. filter传的是表达式,filter_by传的是参数
    res = session.query(User).filter(User.name == "lqz").all()
    res = session.query(User).filter(User.name != "lqz").all()
    res = session.query(User).filter(User.name != "lqz", User.email == '47@qq.com').all()
    res = session.query(User).filter_by(name='lqz099').all()
    res = session.query(User).filter_by(name='lqz099',email='47@qq.com').all()

4. 使用占位符(了解)  :value  :name
    res = session.query(User).filter(text("id<:value and name=:name")).params(value=10, name='lqz099').order_by(User.id).all()

5. 自定义查询(了解)
    res=session.query(User).from_statement(text("SELECT * FROM users where email=:email")).params(email='7@qq.com').all()

高级查询

'''高级查询'''

1. and条件连接
    res = session.query(User).filter(User.id > 1, User.name == 'lqz099').all()

2. between (两者之间)
    res = session.query(User).filter(User.id.between(1, 3), User.name == 'lqz099').all()

3. in
    res = session.query(User).filter(User.id.in_([1,3,4])).all()
    res = session.query(User).filter(User.email.in_(['y@qq.com','r@qq.com'])).all()

4. ~非 >>> 除...外
    res = session.query(User).filter(~User.id.in_([1,3,4])).all()

5. 二次筛选
    res = session.query(User).filter(~User.id.in_(session.query(User.id).filter_by(name='lqz099'))).all()

6. and or条件
    # 导入
    from sqlalchemy import and_, or_

    res = session.query(User).filter(and_(User.id >= 3, User.name == 'lqz099')).all()
    res = session.query(User).filter(or_(User.id < 2, User.name == 'eric')).all()
    res = session.query(User).filter(
        or_(
            User.id < 2,
            and_(User.name == 'lqz099', User.id > 3),
            User.extra != ""
        )).all()

7. 通配符,以e开头,不以e开头(模糊查询)
    res = session.query(User).filter(User.email.like('%@%')).all()
    res = session.query(User.id).filter(~User.name.like('e%'))

8.  分页  eg: 一页2条,查第5页
    res = session.query(User)[2*5:2*5+2]

9. 排序
    res = session.query(User).order_by(User.email.desc()).all()
    res = session.query(Book).order_by(Book.price.asc()).all()

    # 第一个条件重复后,再按第二个条件升序排
    res = session.query(User).order_by(User.name.desc(), User.id.asc()).all()

10. 分组查询
    from sqlalchemy.sql import func

    # 1. 按 price 排序
      res = session.query(Book).group_by(Book.price).all()

    # 2. 分组之后取最大 price, price 之和,最小 price
      res = session.query(
          func.max(Book.price),
          func.sum(Book.price),
          func.min(Book.price)).group_by(Book.price).all()
      print(res)
      for item in res:
          print(item)

    # 3. having
      res = session.query(
      func.max(Book.id),
      func.sum(Book.id),
      func.min(Book.id)).group_by(Book.price).having(func.max(Book.id) > 2).all()

联表操作

1. 联表操作
    select * from person,hobby where person.hobby_id=hobby.id;
    res = session.query(Person, Hobby).filter(Person.hobby_id == Hobby.id).all()

2. join表,默认是inner join,自动按外键关联
    select * from Person inner Hobby on Person.hobby_id=Hobby.id;
    res = session.query(Person).join(Hobby).all()

3. isouter=True 外连,表示Person left join Favor,没有右连接,反过来即可
    select * from Person left Hobby on Person.hobby_id=Hobby.id;
    res = session.query(Person).join(Hobby, isouter=True).all()

4. 自己指定on条件(连表条件),第二个参数,支持on多个条件,用and_,同上
    res = session.query(Person).join(Hobby, Person.id == Hobby.id, isouter=True)

组合(了解)

UNION 操作符用于合并两个或多个 SELECT 语句的结果集

'''union 和 union_all'''

    q1 = session.query(User).filter(User.id > 40)
    q2 = session.query(User).filter(User.id > 38)
    res = q1.union(q2).all()

    q1 = session.query(User.email).filter(User.id > 40)
    q2 = session.query(User.email).filter(User.id > 38)
    res = q1.union_all(q2).all()

基于链表跨表查

1. 一对多
    # 方式一:直接连
      res = session.query(Person, Hobby).filter(Person.hobby_id == Hobby.id,Hobby.id>=2).all()
    # 方式二:join连
      res = session.query(Person).join(Hobby).filter(Person.id>=2).all()

2. 多对多
    # 方式一:直接连
      res = session.query(Boy, Girl,Boy2Girl).filter(Boy.id == Boy2Girl.boy_id,Girl.id == Boy2Girl.girl_id).all()
    # 方式二:join连
      res = session.query(Boy).join(Boy2Girl).join(Girl).filter(Person.id>=2).all()

2 原生sql

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Boy, Girl, Boy2Girl, Hobby, Book, User, Person
from sqlalchemy.orm import scoped_session
from sqlalchemy.sql import text

engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/aaa", max_overflow=0, pool_size=5)
Session = sessionmaker(bind=engine)
session = scoped_session(Session)

# 查询
cursor = session.execute(text('select * from books'))
res = cursor.fetchall()

# 添加
cursor = session.execute(text('insert into users(name, email) values(:value1, :value2)'), params={"value1": 'lqz', "value2": "999@qq.com"})


session.commit()
session.close()

3 一对多, 多对多

3.1. 一对多

表模型

class Hobby(Base):
    __tablename__ = 'hobby'
    id = Column(Integer, primary_key=True)
    caption = Column(String(50), default='篮球')


class Person(Base):
    __tablename__ = 'person'
    id = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=True)
    hobby_id = Column(Integer, ForeignKey("hobby.id"))

    # 跟数据库无关,不会新增字段,只用于快速链表操作
    # 基于对象的跨表查询:就要加这个字段
    # backref用于反向查询
    hobby = relationship('Hobby', backref='pers')

    def __repr__(self):
        return self.name

基本操作

'''新增'''
   person=Person(name='张三',hobby=Hobby(caption='乒乓球'))
   session.add(person)

'''正向查询'''
   person = session.query(Person).filter_by(id=1).first()
   print(person.hobby_id)
   print(person.hobby.caption)


'''反向查询'''
    hobby = session.query(Hobby).filter_by(id=2).first()
    print(hobby.pers)

3.2. 多对多

表模型

class Boy2Girl(Base):
    __tablename__ = 'boy2girl'
    id = Column(Integer, primary_key=True, autoincrement=True)
    girl_id = Column(Integer, ForeignKey('girl.id'))
    boy_id = Column(Integer, ForeignKey('boy.id'))


class Girl(Base):
    __tablename__ = 'girl'
    id = Column(Integer, primary_key=True)
    name = Column(String(64), unique=True, nullable=False)

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name


class Boy(Base):
    __tablename__ = 'boy'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(64), unique=True, nullable=False)

    # 与生成表结构无关,仅用于查询方便,放在哪个单表中都可以
    # 方便快速查询,写了这个字段,相当于django 的manytomany,快速使用基于对象的跨表查询
    girls = relationship('Girl', secondary='boy2girl', backref='boys')

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name

基本操作

'''新增'''
   session.add(Boy(name='李清照', girls=[Girl(name='小红'), Girl(name='小黄')]))
    ||
    ||
   girl1 = Girl(name='小红1')
   girl2 = Girl(name='小黄1')
   boy = Boy(name='李清照1', girls=[girl2, girl1])
   session.add(boy)

'''正向查询'''
   boy = session.query(Boy).filter_by(id=2).first()
   print(boy.girls)

'''反向查询'''
   girl = session.query(Girl).filter_by(id=2).first()
   print(girl.boys)

六. flask-sqlalchemy 和 flask-migrate

1 flask-sqlalchemy

本质上是将上面的一些初始化操作封装到了一个类里面, 使操作更加便捷

1.1. 下载

pip install Flask-SQLAlchemy

1.2. 基本使用

1 导入
    from flask_sqlalchemy import SQLAlchemy

2 实例化得到对象
    db = SQLAlchemy()

3  将db注册到app中
    db.init_app(app)

4 视图函数中使用session  >>>  # 线程安全的
    全局的db.session

5 models.py 中   >>>   之前是继承Base
    现在继承 db.Base

6 写字段
    username = db.Column(db.String(80), unique=True, nullable=False)

2 flask-migrate

和 django 的数据库迁移一样

2.1. 下载

pip install flask-migrate

2.2. 基本使用

1 导入
    from flask_script import Manager
    from flask_migrate import Migrate, MigrateCommand

2 注册
    manager = Manager(app)
    # 使用flask_migrate的Migrate  包裹一下app和db(sqlalchemy对象)
    Migrate(app, db)

3 给flask_script增加一个db命令
    # 把命令增加到flask-script中去
    manager.add_command('db', MigrateCommand)

4 出现3条命令
    python manage.py db init     # 只执行一次,做初始化操作,以后再也不执行了,多出一个migrations文件夹
    python manage.py db migrate  # 等同于django 的makemigrations
    python manage.py db upgrade  # 等同于django 的migrate

3 小案例

完成下面操作后, 执行迁移命令即可

3.1. 目录结构

-flask_project     # 项目名称
    -app01         # app名称
        - static
        - views
        - __ init __.py
        - models.py
    -manage.py
    -settings.py

3.2. 代码

init.py

1. 做初始化操作

2. 这里一定要导入 models 模块

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 第一步:类实例化得到对象
db = SQLAlchemy()

'''
   ** 这里一定要导入 models 模块, 不然 models模块 不会被执行。
'''
from .models import *

def create_app():
    app = Flask(__name__)

    app.config.from_object('settings.DevelopmentConfig')

    #第二步: 将db注册到app中
    db.init_app(app)

    '''
       注册蓝图
    '''

    return app

models.py

from . import db

# 第三步:把db导入,直接继承db.Model
class Users(db.Model):
    """
    用户表
    """
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username

manage.py

from app01 import create_app
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from app01 import db

app = create_app()

# 第一步:初始化出flask_script的manage
manager = Manager(app)

# 第二步:使用flask_migrate的Migrate  包裹一下app和db(sqlalchemy对象)
Migrate(app, db)

# 第三步:把命令增加到flask-script中去
manager.add_command('db', MigrateCommand)

if __name__ == '__main__':
    # app.run()
    manager.run()

setting.py

class BaseConfig(object):
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:455525@127.0.0.1:3306/abc?charset=utf8"
    SQLALCHEMY_POOL_SIZE = 5
    SQLALCHEMY_POOL_TIMEOUT = 30
    SQLALCHEMY_POOL_RECYCLE = -1
    # 追踪对象的修改并且发送信号
    SQLALCHEMY_TRACK_MODIFICATIONS = False
posted @ 2024-07-07 12:04  codegjj  阅读(2)  评论(0编辑  收藏  举报