flask----day06( sqlalchemy快速插入数据 、scoped_session 保证线程安全、 基本增删查改操作 、 和高级查询 、多对多 、 连表查询 、 flask-sqlalchemy使用,注意事项!!! )

.
.
.
.
.
.

今日内容


# 模型表

# 第一步  导入模块
from sqlalchemy import create_engine
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index

# 第二步  执行declarative_base,得到一个基类
Base = declarative_base()


# 第三步  继承生成的Base类
class User(Base):
    # 第四步 写字段并指定类型
    id = Column(Integer, primary_key=True)  # Column是生成一列的关键字
    # varchar类型32长度,该字段添加索引,nullable=False 不允许为空
    name = Column(String(32), index=True, nullable=False)
    email = Column(String(32), unique=True)

    # datetime.datetime.now  # 不能加括号,加了括号以后永远是当前时间
    ctime = Column(DateTime, default=datetime.datetime.now)
    extra = Column(Text, nullable=True)  # 文本类型不能为空

    # 第五步  写表名  如果不写以类名为表名
    __tablename__ = 'users'

    # 第六步 建立联合索引,建立联合唯一
    __table_args__ = (
        UniqueConstraint('id', 'name', name='uix_id_name'),
        Index('ix_id_name', 'name', 'email', )
        # uix_id_name 就是联合唯一的名字    ix_id_name 就是联合索引的名字
    )

    # 直接打印对象时触发
    def __str__(self):
        return self.name

    # 打印一个容器,容器中如果有一个个对象会触发
    def __repr__(self):
        return self.name


class Book(Base):
    id = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=False)
    __tablename__ = 'books'


# 第七步 把表同步到数据库中
# 还是要先生成引擎
# 不会创建库,只会生成表,会在你配置的库里面生成表
engine = create_engine(
    'mysql+pymysql://root:222@127.0.0.1:3306/20230113bbs?charset=utf8',
    max_overflow=0,  # 超过连接池大小后,还可以再创建的连接数
    pool_size=10,  # 连接池大小,sqlalchemy自带连接池!!!!!
    pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
    pool_recycle=600,  # 600s后,对该连接的回收,再创一个新的连接,-1表示线程不回收一直用
)

# 把表同步到数据库命令 (把被Base管理的所有表(继承base的表),都同步到数据库)
# 这样只要右键运行该py文件,就可以直接将表同步到数据库了
Base.metadata.create_all(engine)

.
.
.
.
.
.
.

1 sqlalchemy快速插入数据


# sqlalchemy是什么 orm框架,跟其他web框架没有必然联系,可以独立使用

# 安装,快速使用,执行原生sql


# 创建表和删除表
	-不能创建数据库
	-不能修改字段(增加,删除)


.
.
.
.
.
.
.
.

sqlalchemy往表里面插入数据


# 使用orm插入数据
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker


# 第一步 生成引擎
engine = create_engine(
    'mysql+pymysql://root:222@127.0.0.1:3306/20230113bbs?charset=utf8',
    max_overflow=0,  # 超过连接池大小后,还可以再创建的连接数
    pool_size=10,  # 连接池大小,sqlalchemy自带连接池!!!!!
    pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
    pool_recycle=10,  # 连接被用10次后,对该连接的回收,再创一个新的连接,-1表示线程不回收一直用
)

# 第二步 用sessionmaker类产生一个Session类,传入engin
Session = sessionmaker(bind=engine)


# 注意sqlalchemy这里的session和flask里面session没有半毛钱关系!!!


# 第三步  得到db_session对象(会话对象)  相当于pymysql里面的conn连接对象
# 为了区分变量名起成db_session
db_session = Session()


# 第四步  往表里面增加数据  固定步骤
from src.admin.models import Book

book_obj = Book(name='红楼梦1')  # 先产生对象
db_session.add(book_obj)
db_session.commit()

# 关闭会话对象
db_session.close()


# sessionmaker类源码4899行,self.class_ = type(class_.__name__, (class_,), {})
# class_ = Session
# type类是元类可以产生类,
变量名 = type( 类的真实名,由父类名称组成的元组(可以为空),包含属性的字典(名称和值))

# 所以上面的就是产生一个  类名叫Session 继承了Session类  并且没有给创建的类添加任何属性与方法


.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

2 使用scoped_session 保证线程安全

2.1 基本使用


# orm.py

# 使用orm插入数据
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 第一步 生成引擎
engine = create_engine(
    'mysql+pymysql://root:222@127.0.0.1:3306/20230113bbs?charset=utf8',
    max_overflow=0,  # 超过连接池大小后,还可以再创建的连接数
    pool_size=10,  # 连接池大小,sqlalchemy自带连接池!!!!!
    pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
    pool_recycle=10,  # 连接被用10次后,对该连接的回收,再创一个新的连接,-1表示线程不回收一直用
)

# 第二步 用sessionmaker类产生一个Session类,传入engin
Session = sessionmaker(bind=engine)

# 注意sqlalchemy这里的session和flask里面session没有半毛钱关系!!!

------------------------------------------------------

from . import order_bp
from src.admin.models import Book
from src.orm import Session

count = 0

# 1 这个是将db_session连接对象放在了视图函数里面生成
@order_bp.route('/home', defaults={'name': 'teng'})
def home(name):
    global count
    count += 1
    # 访问该视图函数,就往book表里插入一条数据
    book_obj = Book(name='水浒传-%s' % count)

    # db_session 放到视图函数外面还是里面???
    # 放外面,全局都用这一个db_session连接对象,并发情况下会出现数据错乱情况的
    # 放在视图函数里面,每次请求使用一个新的db_session连接对象
    db_session = Session()
    db_session.add(book_obj)
    db_session.commit()
    print(count)
    return 'haha'


------------------------------------

count1 = 0
from sqlalchemy.orm import scoped_session
# db_session = Session()  # 线程不安全,用scoped_session类包一下
# 这样产生的连接对象,就可以放在全局用了
db_session = scoped_session(Session)


# 2 这个是用的scoped_session类产生的全局db_session连接对象
@order_bp.route('/index', defaults={'name': 'teng'})
def index(name):
    global count1
    count1 += 1
    # 访问该视图函数,就往book表里插入一条数据
    book_obj = Book(name='三国演义-%s' % count1)

    # 放外面,全局都用这一个db_session连接对象,并发情况下会出现数据错乱情况的
    # 又想将db_session连接对象放全局,又想不发送并发安全,怎么办
    # sqlalchemy使用scoped_session来解决

    db_session.add(book_obj)
    db_session.commit()
    print(count1)
    return 'haha'


-----------------------------------------------

# 如何做成线程安全的 ?
scoped_session类191行
self.registry = ThreadLocalRegistry(session_factory)

ThreadLocalRegistry类里面的672行
self.registry = threading.local()


# 内部使用了threading模块里面local对象,
# local对象为每一个线程建立一组键值对,值对应一个小字典,小字典里放该线程放进去的数据!!!
# 取当前线程的session,如果当前线程有,就直接返回用,
# 如果没有,创建一个,放到local对象中


db_session = scoped_session(Session)
# db_session不是Session类的对象了,但是它有Session类对象的所有方法!!!
# db_session 是  scoped_session 的对象


scoped_session类上面有个装饰器

@create_proxy_methods(Session,
                      ":class:`_orm.Session`",
                      ":class:`_orm.scoping.scoped_session`",
                       classmethods=[...],
                       methods=[...],
                       attributes=[...],
                      )
# 把methods与attributes里面对应的属性都注册到了scoped_session类里面去了!!!


# 以后全局使用db_session即可,它线程安全


.
.
.
.
.
.
.
.

3 基本增删查改


# 增,删,改 查 基本查询  和  高级查询


from src.admin.models import User, Book
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from sqlalchemy.sql import text

engine = create_engine('mysql+pymysql://root:222@127.0.0.1:3306/20230113bbs?charset=utf8',
                       max_overflow=0,  # 超过连接池大小后,还可以再创建的连接数
                       pool_size=10,  # 连接池大小,sqlalchemy自带连接池!!!!!
                       pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
                       pool_recycle=10,  # 连接被用10次后,对该连接的回收,再创一个新的连接,-1表示线程不回收一直用)
                       )

Session = sessionmaker(bind=engine)
db_session = scoped_session(Session)

----------------------------------------

# 1  增加:add   add_all

book_obj1 = Book(name='nimadan1')
book_obj2 = Book(name='nimadan2')
user_obj = User(name='lqz', email='22@qq.com', extra='哈哈哈')

# db_session.add(book_obj)  添加一条数据
# book表数据与user表数据可以一次性添加!!!
# 批量添加数据,不同表模型的对象,可以一次性添加!!!
db_session.add_all([book_obj1, book_obj2, user_obj])

db_session.commit()
db_session.close()

----------------------------------------------------------------
----------------------------------------------------------------

# 2 基本查  filter  filter_by     filer:写条件     filter_by:等于的值

# 2.1 db_session.query(User)   中写表模型名 相当于 select * from User;
# 2.2 filter 过滤条件,必须写  表达式  ==  >=  !=  > 等
# 2.3 all() 的结果是普通列表里面有查出来的所有对象      first()的结果是单个对象


res = db_session.query(User).filter(User.name == 'lqz')  # 还没有真正的执行,只是生成一个sql语句
print(res)  # 执行结果是打印sql语句


# filter或filter_by过滤之后,要点all或者first才会执行sql语句

# 点first()会拿到对象, 只会拿查到的所有的对象中的第一个
user = db_session.query(User).filter(User.name == 'lqz').first()
print(user.email)


# 点all()会拿到查到的所有对象  放在一个普通的列表中!!!
user1 = db_session.query(User).filter(User.id > 1).all()[0]
print(user1.email)


# filter_by  直接写等式    不能写成 User.name = 'lqz'
user = db_session.query(User).filter_by(name='lqz').first()
print(user.email)

user1 = db_session.query(User).filter_by(id=2).first()
print(user1.email)


-----------------------------------------------------------


# 3 删除(查到才能删)
filter或filter_by查询的结果  不要all或first出来,直接 .delete() 即可

res = db_session.query(User).filter_by(id=2).delete()
db_session.commit()   # 一定不要忘了
print(res)  # 影响的行数

-----------------------------------------------------------

# 4 修改(要先查到,才能改)

# 方式一:update修改
res = db_session.query(User).filter_by(id=3).update({"name": "彭于晏1"})

res1 = db_session.query(User).filter_by(id=3).update({User.name: "彭于晏2"})
# 批量修改怎么办?   过滤条件查出来的是多个对象,该orm语句就是批量修改操作!!!

res2 = db_session.query(User).filter_by(id=3).update({User.name: User.name + '888'}, synchronize_session=False )

# synchronize_session=False 的作用就是执行不同步操作,不写默认就是False,
# 尤其是删除操作有时会报错,就需要加这句话了

# 也可以让该字段数据的数字在原有的基础上加对应的数字,比如年龄加5岁
res3 = db_session.query(User).filter_by(id=3).update({User.age: User.age + 5}, synchronize_session='evaluate')


db_session.commit()  # 最后id为3的记录里name字段对应的名字改为了:  彭于晏2888

-----------------------------------

# 方式二,使用对象修改  适用于批量修改的场景  先all查出所有对象出来,再for循环出来,一个个的改
res = db_session.query(User).filter_by(id=3).first()
res.name='zzz'

db_session.add(res)  # add的对象 如果有主键,就是修改,如果没有主键就是新增
# 上面这句话不要,直接执行下面这句话好像也行!!! 对象改完属性直接commit好像也行!!!

db_session.commit()

.
.
.
.
.
.
.
.
.
.
.

3.1 基本增删查改和高级查询


# 4 查询: filer:写条件     filter_by:等于的值

# 4.1 查询所有  是list对象
res = db_session.query(User).all()  # 结果是个普通列表
print(type(res))
print(len(res))

----------------------------------------------

# 4.1.1 可以只查询   表里面的某几个字段
select name as xx,email from user  # 原生sql对应的orm语句
res = db_session.query(User.name.label('xx'), User.email).all()
print(res)  # [('laoliu', '33@qq.com'), ('lqz', '22@qq.com')]  列表套元祖的形式

res = db_session.query(User.name.label('xx'), User.email)
print(res)  # 不点all,会打出原生sql语句出来

----------------------------------------------

# 4.1.2 filter 括号里面写的是表达式,而且字段名前要制定表名
res = db_session.query(User).filter(User.name == "lqz").all()

# 多个条件默认是and
res = db_session.query(User).filter(User.name != "lqz", User.email == '3@qq.com').all()



# filter_by 括号里面写的就是字段对应的值即可,扩展性没filter高,写条件不方便
res = db_session.query(User).filter_by(name='lqz099').all()
print(len(res))

----------------------------------------------

# 4.2 db_session.query(User)生成的是query对象,可以直接点first()
#  query对象点all后是python中普通的列表,列表没有first方法
res = db_session.query(User).first()

----------------------------------------------

# 4.3 使用占位符查询    :value      :name
select * from user where id <2 or name=lqz  # 原生sql
res = db_session.query(User).filter(text("id<:value or name=:name")).params(value=2, name='lqz').all()

-----------------------------------------------

# 4.4 自定义查询(了解)

# from_statement  写纯原生sql语句查询
select * from user where email='3@qq.com'  # 原生sql
res=db_session.query(User).from_statement(text("SELECT * FROM users where email=:email")).params(email='3@qq.com').all()
print(res)

-----------------------------------------------

.
.
.
.
.
.
.

filter 高级查询


# 4.5 高级查询   条件表达式

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


# between
res = db_session.query(User).filter(User.id.between(1, 9), User.name == 'lqz').all()


# in_    应该是为了避免和关键字冲突加了个杠
res = db_session.query(User).filter(User.id.in_([1,3,4])).all()
res = db_session.query(User).filter(User.email.in_(['3@qq.com','r@qq.com'])).all()



#  ~  非
res = db_session.query(User).filter(~User.id.in_([1,3,4])).all()
print(res)

--------------------------------------------

# 二次筛选    类似于sql里面的子查询   一个查询的结果作为另一个查询的条件
res =db_session.query(User).filter(
                          User.id.in_( db_session.query(User.id).filter_by(name='lqz') )
                                ).all()
print(res)


# and or条件   or_包裹的都是or条件
from sqlalchemy import and_, or_

res = db_session.query(User).filter(User.id < 3, User.name == 'lqz099').all()  # and条件

res = db_session.query(User).filter(or_(User.id < 2, User.name == 'eric')).all()  # or条件

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



# 通配符, 查询email中含有@符的所有数据
res = db_session.query(User).filter( User.email.like('%@%') ).all()


# 以e开头
select user.id from user where  user.name like e%;   # 原生sql语句
res = db_session.query(User.id).filter(User.name.like('e%'))

.
.
.
.
.
.
.
.

分页与排序查询


# 分页
# 一页2条,查第5页
res = db_session.query(User)[2*5:2*5+2]   # 就是拿 从第10条到第12条数据


# 排序
res = db_session.query(Book).order_by(Book.price.desc()).all()
res = db_session.query(Book).order_by(Book.price.asc()).all()


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


.
.
.
.
.
.

分组查询


# 分组查询  5个聚合函数
from sqlalchemy.sql import func

res = db_session.query(User).group_by(User.extra)  # 按extra字段分组
# 如果是严格模式,该orm语句会错,严格模式下,只能查分组的字段和聚合函数字段


# 分组之后取    分组的字段    最大id  id之  和  最小id
res = db_session.query(
     User.extra,
     func.max(User.id),
     func.sum(User.id),
     func.min(User.id)).group_by(User.extra).all()

for item in res:
    print(item[2])

------------------------------------------------------

# 分组后再过滤
# having
select max(id),sum(id),min(id) from  user group by  user.extra   having id_max>2;
res = db_session.query(
        func.max(User.id),
        func.sum(User.id),
        func.min(User.id)).group_by(User.extra).having(func.max(User.id) > 2)

.
.
.
.
.
.
.
.
.
.
.
.

3.2 原生sql


### 方式一:

# 第一步:导入
from sqlalchemy import create_engine

# 第二步:生成引擎对象
engine = create_engine(
    "mysql+pymysql://root@127.0.0.1:3306/cnblogs",
    max_overflow=0,  # 超过连接池大小外最多创建的连接
    pool_size=5,  # 连接池大小
    pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
    pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
                      )

# 第三步:使用引擎获取连接,操作数据库
conn = engine.raw_connection()  # 这句话获取连接,应该放在视图内部

cursor=conn.cursor(pymysql.cursors.DictCursor)
cursor.execute('select * from aritcle')
print(cursor.fetchall())


-------------------------------------------------------


### 方式二:
from models import User, Book
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session

engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa")
Session = sessionmaker(bind=engine)
db_session = scoped_session(Session)


# 2.0.9 版本需要使用text包裹一下,原来版本不需要
cursor = db_session.execute(text('select * from users'))
result = cursor.fetchall()
print(result)

cursor = db_session.execute(text('insert into books(name) values(:name)'), params={"name": '红楼梦'})
session.commit()

session.close()

.
.
.
.
.
.
.

3.3 django中执行原生sql


# 选择的查询基表Book.objects.raw ,只是一个傀儡,正常查询出哪些字段,都能打印出来

def index(request):
    books = Book.objects.raw('select * from app01_book where id=1')
    # RawQuerySet  用起来跟列表一样

    print(books[0])
    print(type(books[0]))


    res = Book.objects.raw('select * from app01_publish where id=1')
    # RawQuerySet  用起来跟列表一样

    print(res[0])
    print(type(res[0]))
    print(res[0].name)
    print(res[0].addr)


    return HttpResponse('ok')

.
.
.
.
.
.
.
.
.
.
.

4 一对多



# 一对一:本身是一个表,拆成两个表,做一对一的关联;  本质就是一对多,只不过关联字段唯一
# 一对多:关联字段写在多的一方

# 多对多:需要建立中间表;本质也是一对多

# 本质就只有一种外键关系

.
.
.

4.1 表模型



# 一对多关系
from sqlalchemy import create_engine
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
from sqlalchemy.orm import relationship


# 第二步:执行declarative_base,得到一个类
Base = declarative_base()


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指的是tablename而不是类名
    # 关联字段写在多的一方,写在Person中,跟hobby表中id字段做外键关联
    hobby_id = Column(Integer, ForeignKey("hobby.id"))


    # 该虚拟的hobby字段,跟数据库无关,不会新增字段,只用于快速链表操作
    # 基于对象的跨表查询: 就要通过这个虚拟字段,才能跳到关联的表
    # person对象.hobby得到关联的对象
    # pserson对象.hobby_id 只会拿到hobby_id字段对应的值,不会调到关联表里面去,和django有点小区别
    # 类名,backref用于反向查询,比如如果有hobby对象,hobby.pers 拿到所有关联的人对象了!!!
    hobby = relationship('Hobby', backref='pers')

    def __repr__(self):
        return self.name


engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa", )

# 把表同步到数据库  (把被Base管理的所有表,都创建到数据库)
Base.metadata.create_all(engine)

# 把所有表删除
# Base.metadata.drop_all(engine)

.
.
.
.
.
.
.
.

4.2 新增和基于对象的查询


from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from models1 import Hobby, Person

engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa")
Session = sessionmaker(bind=engine)
db_session = scoped_session(Session)

------------------------------------------------------------

# 一对多关系  新增

hobby=db_session.query(Hobby).filter(Hobby.caption=='乒乓球').first()
db_session.add(hobby)

person = Person(name='王五',hobby_id=1)
db_session.add(person)
db_session.commit()

------------------------------

# 支持按对象的增加方式,必须加relationship 做关联
# 方式一
hobby=db_session.query(Hobby).filter(Hobby.caption=='乒乓球').first()
person = Person(name='赵六',hobby=hobby)  # 一旦按对象添加,前面就要用虚拟字段了


# 方式二
hobby = Hobby(caption='羽毛球')  # 产生一条数据对象,还没提交,表中暂时没有
person = Person(name='赵六', hobby=hobby)  # 产生一条数据对象,还没提交,表中暂时没有
db_session.add_all([person, hobby])
db_session.commit()   # 一起提交

--------------------------------------------------------------

## 基于对象的跨表查询

# 正向查询
person=db_session.query(Person).filter(Person.name=='王五').first()

print(person.hobby)  # 通过person对象点虚拟的hobby字段,得到外键关联的hobby对象
print(person.hobby_id)  # 得到的是person对象对应的hobby_id字段对应的实际值,不会跳到关联表去


# 反向查询
hobby=db_session.query(Hobby).filter(Hobby.id==1).first()
print(hobby.pers)  # 列表里面放对应的所有的,多的对象
for i in hobby.pers:
    print(i.name)

# 还有一种假设要查Hobby表对应id==1 关联的Person表里的所有对应的多的对象
直接就查出来了  person_all_obj = db_session.query(Person).filter(hobby_id==1).all()

--------------------------------------------------------------

# 基于连表的查询(一会讲)

.
.
.
.
.
.
.
.
.

5 多对多

5.1 表模型


# 多对多关系
from sqlalchemy import create_engine
import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
from sqlalchemy.orm import relationship


# 第二步:执行declarative_base,得到一个类
Base = declarative_base()


# 中间表  手动创建
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')




engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa", )


# 把表同步到数据库  (把被Base管理的所有表,都创建到数据库)
Base.metadata.create_all(engine)

# 把所有表删除
# Base.metadata.drop_all(engine)

.
.
.
.
.
.
.
.

5.2 增加和基于对象的跨表查询 多对多关系


from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from models2 import Girl, Boy, Boy2Girl

engine = create_engine("mysql+pymysql://root@127.0.0.1:3306/aaa")
Session = sessionmaker(bind=engine)
db_session = scoped_session(Session)

-------------------------------------------------

# 新增
# 1 笨办法新增
girl=Girl(name='刘亦菲')
boy=Boy(name='彭于晏')
db_session.add_all([girl,boy])
db_session.add(Boy2Girl(girl_id=1,boy_id=1))
db_session.commit()


# 2 使用relationship
boy = Boy(name='lqz')
boy.girls = [Girl(name='迪丽热巴'), Girl(name='景田')]
db_session.add(boy)
db_session.commit()

-------------------------------------------------

# 基于对象的跨表查询
# 正向
boy = session.query(Boy).filter(Boy.id==2).first()
print(boy.girls)

# 反向
girl = session.query(Girl).filter(Girl.id==2).first()
print(girl.boys)


# 如果没有relationship,纯自己操作   怎么纯自己操作? 看下老刘视频



# 基于连表的查询(一会讲)

.
.
.
.
.
.
.

6 连表查询


# 一对多关联关系,基于连表的跨表查询
from models3 import Person,Hobby


# 不建议使用的连表方式!!!
res = db_session.query(Person, Hobby).filter(Person.hobby_id == Hobby.id).all()
select * from person,hobby where person.hobby_id=hobby.id;  # 对应的原生sql语句
# 连表操作,这种通过笛卡尔积的方式让person与hobby里面的数据相乘,
# 很多乘很多时,就可能会直接卡死!!!所以用笛卡尔积的方式连表并不合理!!

------------------------------------------------------------
------------------------------------------------------------


# 自己连表查询
# join, 默认是inner join,  自动按外键关联  join括号里面要放,要连接的表的表名
# 别放成起始表的表名!!!
select * from Person inner join Hobby on Person.hobby_id=Hobby.id; # 原生sql语句
res = db_session.query(Person).join(Hobby,).all()  # 自动按外键关联,有外键关联才能这样写
res = db_session.query(Person).join(Hobby,Person.hobby_id==Hobby.id).all()  # 一样的



# isouter=True 外连,       没有右连接,反过来即可
select * from Person left join Hobby on Person.hobby_id=Hobby.id;  # 原生sql语句
res = db_session.query(Person).join(Hobby, isouter=True).all()  # 表示Person左连接Hobby表
res = db_session.query(Person).outerjoin(Hobby,).all()  # 也可以这样写
# outerjoin相当于LEFT OUTER JOIN 左外连接;outerjoin( ) 返回结果有null
# 注意query括号里面放表名,代表的是要查询该表里的所有字段!!!!

# 还可以这样写,指定起始表名,一般在多张表连接的时候,习惯这样写,能够让结构更清晰!!!
res = db_session.query(Person).select_from(Person).outerjoin(Hobby,Person.hobby_id=Hobby.id).all()






# 没有right join,通过这个实现
res = db_session.query(Hobby).join(Person, isouter=True).all()
res = db_session.query(Hobby).outerjoin(Person,).all()  # 或这样



------------------------------------------------------------

# 自己指定on条件(连表条件),第二个参数,支持on多个条件,用and_,同上

select * from Person left join Hobby on Person.id=Hobby.id;  # 原生sql语句


res = db_session.query(Person).join(Hobby, Person.hobby_id == Hobby.id, isouter=True)
#  sql本身有问题,只是举个例子

print(res)

------------------------------------------------------------
------------------------------------------------------------

# 示例
# 内连接
res1 = db_session.query(User1.name, Age1.age).join(Age1, User1.age_id == Age1.id).all()
res11 = db_session.query(User1.name, Age1.age).join(Age1, User1.age_id == Age1.id)
print(res1)  # [('lihua', 55), ('lqz', 56), ('haha', 57), ('haha2', 58)]
print(res11)  # SELECT user1.name AS user1_name, age1.age AS age1_age FROM user1 INNER JOIN age1 ON user1.age_id = age1.id


# 左连接
res2 = db_session.query(User1.name, Age1.age).outerjoin(Age1, User1.age_id == Age1.id).all()
res22 = db_session.query(User1.name, Age1.age).outerjoin(Age1, User1.age_id == Age1.id)
print(res2)  # [('lihua', 55), ('lqz', 56), ('jason', 57), ('jack', 58), ('john', None), ('jocker', None)]
print(res22)  # SELECT user1.name AS user1_name, age1.age AS age1_age FROM user1 LEFT OUTER JOIN age1 ON user1.age_id = age1.id

# 左连接
res3 = db_session.query(User1.name, Age1.age).join(Age1, User1.age_id == Age1.id, isouter=True).all()
res33 = db_session.query(User1.name, Age1.age).join(Age1, User1.age_id == Age1.id, isouter=True)
print(res3)  # [('lihua', 55), ('lqz', 56), ('jason', 57), ('jack', 58), ('john', None), ('jocker', None)]
print(res33)  # SELECT user1.name AS user1_name, age1.age AS age1_age FROM user1 LEFT OUTER JOIN age1 ON user1.age_id = age1.id

--------------------------------------------------------------

.
.
.
.
.
.
.
.
.
.

多对多关系连表查询


# 多对多关系连表查询

#方式一:直接连  一般不这样用,查询效率很低
res = db_session.query(Boy, Girl,Boy2Girl).filter(Boy.id == Boy2Girl.boy_id,Girl.id == Boy2Girl.girl_id).all()


# 方式二:join连表   方便高效
res = db_session.query(Boy).join(Boy2Girl).join(Girl).filter(Boy.id>=2).all()


.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

6 flask-sqlalchemy模块的使用


# 有个第三方flask-sqlalchemy,帮助咱们快速的集成到flask中


#  使用flask-sqlalchemy模块的步骤

from flask_sqlalchemy import SQLAlchemy  # 1 导入
db = SQLAlchemy()      # 2 # SQLAlchemy类实例化生成db对象
# 后续我们去操作数据库的时候 db.session 生成的
# 就是原来的我们用scoped_session(Session类)生成的连接对象db_session


db.init_app(app)    # 3  将db注册到app中  一般写在蓝图注册代码的下面
# 上面几几句话,一般写在项目的包文件下的__init__文件里 从创建app对象、生成db对象、注册蓝图对象、
# 最后将db对象注册到app对象中去,这样项目包下的__init__文件差不多也就写完了


# 4 视图函数中使用数据库连接对象,
# 直接导入__init__的db对象,用db.session 就拿到了连接对象了!!!
	全局的db.session  # 线程安全的



# 5 models.py 中模型类要继承Model
# 原来用sqlalchemy的时候模型类继承的是Base       Base=declarative_base() c产生的
class User(db.Model):
    username = db.Column(db.String(80), unique=True, nullable=False)


# 6 写字段,需要用db.Column()          原来sqlalchemy是直接 Column()
	username = db.Column(db.String(80), unique=True, nullable=False)



# 7 配置文件中配置下连接地址与数据库连接池参数,就是我们之前生成的引擎对象的参数
# 这个样在生成db对象的时候,就已经把引擎的参数绑定进去了
    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root@127.0.0.1:3306/ddd?charset=utf8"
    SQLALCHEMY_POOL_SIZE = 100    # 设置连接池的大小,别设太小
    SQLALCHEMY_POOL_TIMEOUT = 600  # 池中没有连接,最多等待时间,超过还没拿到就报错!!!
    SQLALCHEMY_POOL_RECYCLE = 1800  # -1 表示不回收  1800秒后回收掉该连接对象
    # 追踪对象的修改并且发送信号
    SQLALCHEMY_TRACK_MODIFICATIONS = False


# 池数量设的小,超时等待时间也设定小,容易触发超时等待的报错,导致后端崩掉!!!


------------------------------------------------------

.
.

flask-sqlalchemy 使用代码示范


src目录下的init文件代码

from flask import Flask, request, current_app
from flask_cors import CORS

# 引入Flask-SQLAlchemy
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()  # 实例化SQLAlchemy

# 引入蓝图
from .blueprints import auth
from .blueprints import organization


# 引入模型
from app_flask_service.models.flasksqlalchemy_models_app import *

import settings
import logging
import logging.handlers


# 创建app
def create_app():
    app = Flask(__name__)
    CORS(app, resources=r'/*')  #解决跨域的
    app.debug = False
    app.secret_key = 'shdtw'  # 自定义的session秘钥

    # 设置配置文件
    product_cfg = settings.ProductionConfig()
    app.config.from_object(product_cfg)

    # 定义日志记录器
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger('app')
    logger.setLevel(logging.INFO)

    # 配置 logger 写入 rotating日志文件
    file_handler = logging.handlers.RotatingFileHandler('./logs/log_app.log', maxBytes=1024 * 1024 * 10, backupCount=5)
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
    logger.addHandler(file_handler)


    # 注册蓝图
    app.register_blueprint(auth.blueprint, url_prefix='/auth')
    app.register_blueprint(organization.blueprint, url_prefix='/organization')


    db.init_app(app)

    return app

-----------------------------------------

settings配置文件

TOKEN_EXPIRE_TIME = 3600  # token生成时设置的过期时间(秒)

APP_DB_CONF = {
    'DB_type': 'mysql',
    'DBAPI': 'pymysql',
    'IP_address': '192.168.11.99',  # 本机:'localhost', 生产环境:'58.40.119.146',
    'port': '3306',  # 本机:'3306', 生产环境:'xxxx',
    'username': 'root',
    'pwd': 'root123',
    'path': 'shctwl_qr_jingan'
}


## 基础信息配置信息 -->

def db_uri(config):
    return "{db_type}+{dbapi}://{username}:{pwd}@{ip_address}:{port}/{path}".format(
        db_type=config['DB_type'],
        dbapi=config['DBAPI'],
        username=config['username'],
        pwd=config['pwd'],
        ip_address=config['IP_address'],
        port=config['port'],
        path=config['path']
    )


class BaseConfig(object):
    JSON_AS_ASCII = False
    JSON_SORT_KEYS = False
    JSONIFY_MIMETYPE = "application/json;charset=utf-8"

    SQLALCHEMY_DATABASE_URI = db_uri(APP_DB_CONF) + "?charset=utf8"  # 数据库用户名:密码@host:port/数据库名?编码
    SQLALCHEMY_TRACK_MODIFICATIONS = False  # 如果设置成 True (默认情况),Flask-SQLAlchemy 将会追踪对象的修改并且发送信号。这需要额外的内存, 如果不必要的可以禁用它。
    # SQLALCHEMY_ECHO = True

    '''session绑定redis'''
    '''
    SESSION_TYPE = 'redis'  # session类型为redis
    SESSION_KEY_PREFIX = 'session:'  # 保存到session中的值的前缀
    SESSION_PERMANENT = False  # 如果设置为True,则关闭浏览器session就失效。
    SESSION_USE_SIGNER = False  # 是否对发送到浏览器上 session:cookie值进行加密
    '''


class ProductionConfig(BaseConfig):
    SQLALCHEMY_POOL_SIZE = 100
    SQLALCHEMY_POOL_TIMEOUT = 600
    SQLALCHEMY_POOL_RECYCLE = 800
    SQLALCHEMY_MAX_OVERFLOW = 50  # 控制在连接池达到最大值后可以创建的连接数。当这些额外的 连接回收到连接池后将会被断开和抛弃。

    SQLALCHEMY_ENGINE_OPTIONS = {
        'pool_size': 100,
        'pool_timeout': 600,
        'pool_recycle': 800,
        'max_overflow': 50,
        'pool_pre_ping': True
    }


class DevelopmentConfig(BaseConfig):
    SQLALCHEMY_POOL_SIZE = 50
    SQLALCHEMY_POOL_TIMEOUT = 300
    SQLALCHEMY_POOL_RECYCLE = -1
    SQLALCHEMY_MAX_OVERFLOW = 20  # 控制在连接池达到最大值后可以创建的连接数。当这些额外的 连接回收到连接池后将会被断开和抛弃。


class TestingConfig(BaseConfig):
    SQLALCHEMY_POOL_SIZE = 50
    SQLALCHEMY_POOL_TIMEOUT = 300
    SQLALCHEMY_POOL_RECYCLE = -1
    SQLALCHEMY_MAX_OVERFLOW = 20  # 控制在连接池达到最大值后可以创建的连接数。当这些额外的 连接回收到连接池后将会被断开和抛弃。

----------------------------------------

# 启动文件   service_server.py
import os, sys

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from gevent import monkey

monkey.patch_all()

from app_flask_service import *
from utils.uwsgi_server.server_gevent import *     # 见下面的文件

# from utils.uwsgi_server.server_flask_debug import *

app = create_app()

if __name__ == "__main__":
    print('垃圾品质识别系统接口服务启动……')
    server = uwsgi_server(app=app, address='0.0.0.0', port=5000)
    # server = uwsgi_server(app=app, address='0.0.0.0', port=39090)
    server.start()

----------------------------------------------

# server_gevent.py文件

import os,sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from gevent.pywsgi import WSGIServer

import urllib3
urllib3.disable_warnings()


class uwsgi_server():
    def __init__(self,app,address='0.0.0.0',port=5000) -> None:
        self.server = WSGIServer((address, port), app)

    def start(self):
        self.server.serve_forever()

.
.
.

使用flask-sqlalchemy模块的时候注意事项!!!


# 一般把创app的代码都封装到create_app方法中
# 使用flask_sqlalchemy的时候,如果不在视图函数操作数据库或者使用Flask的全局变量,
# 需要先
app.app_context():
    把操作数据库的代码写在这儿

# 不然会报错!!!  说什么不在请求上下文里面什么的!!!

.
.
.
.
.
.
.
.
.
.

7 flask-migrate使用


# 表发生变化,都会有记录,自动同步到数据库中

# 原生的sqlalchemy,不支持修改表的
# flask-migrate可以实现类似于django的
	python manage.py makemigrations # 记录
	python manage.py migrate        # 真正的同步到数据库

-----------------------------------------------------------

# 使用步骤
	 flask:2.2.2   flask-script:2.0.3

第一步:安装,依赖于flask-script
	pip3.8 install flask-migrate==2.7.0

---------------------

2 在app所在的py文件中
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
manager = Manager(app)
Migrate(app, db)
manager.add_command('db', MigrateCommand)
manager.run()    # 以后使用python manage.py runserver 启动项目


3 以后第一次执行一下
python manage.py db init  # 生成一个migrations文件夹,里面以后不要动,记录迁移的编号


4 以后在models.py 写表,加字段,删字段,改参数


5 只需要执行
python manage.py db migrate  # 记录
python manage.py db upgrade  # 真正的同步进去

.
.
.
.
.
.
.
.
.
.
.
.

加在类上的装饰器


def speak():
    print('说话了')

def wrapper(func):
    def inner(*args, **kwargs):
        res = func()
        res.name = 'lqz'
        res.speak = speak
        return res
    return inner


@wrapper  # 语法糖会把Person当参数传入到装饰器中   Person=wrapper(Person)
class Person:
    pass

p = Person()

print(p.name)   # 通过加在类上的装饰器,给类实例化出来的对象,添加新的属性与方法
p.speak()

.
.
.
.

sqlalchemy 的orm语法里面的filter与where的区别


在SQLAlchemy的ORM语法中,`filter`和`where`都用于添加条件来筛选查询结果,但它们在使用上有一些区别。

1. `filter`方法:`filter`是ORM语法中常用的方法,它用于添加查询条件。
它接受一个或多个表达式作为参数,每个表达式表示一个筛选条件。这些条件将使用逻辑与(AND)进行组合,
并在生成的SQL查询中作为WHERE子句的一部分。

   示例:

   # 使用filter添加多个条件
   query = session.query(User).filter(User.username == 'John', User.age >= 30)

   # 使用filter添加单个条件
   query = session.query(User).filter(User.username.like('%Smith%'))

   `filter`方法可以连续调用,它们将逐步添加条件,最终生成一个组合了所有筛选条件的查询。

-----------------------------------

2. `where`方法:`where`方法用于在查询中添加原始的SQL WHERE子句。它接受一个SQLAlchemy的文本表达式(`text`)作为参数,该表达式表示完整的WHERE子句。`where`方法更适合于需要使用自定义SQL条件的高级查询场景。

   示例:
   # 使用where添加自定义的SQL WHERE子句
   query = session.query(User).where(text("username = 'John' AND age >= 30"))

   `where`方法允许您直接传递SQL语句的片段作为条件,并且不会进行任何自动参数绑定或表达式解析。

总体而言,`filter`方法更常用且更易于使用,它允许您以更Pythonic的方式构建查询条件,而无需直接编写SQL语句。而`where`方法更适用于需要更高级、自定义SQL条件的场景,它提供了更大的灵活性和控制权。

-----------------------------------

.
.
.

posted @   tengyifan  阅读(95)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示