SQLAlchemy--基本增删改查
简介
SQLAlchemy是一个基于Python实现的ORM框架。该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
安装
pip3 install sqlalchemy
组成部分
- Engine:框架的引擎
- Connection Pooling:数据库连接池
- Dialect:选择连接数据库的DB API种类
- Schema/Types:架构和类型
- SQL Exprression Language:SQL表达式语言
SQLAlchemy本身无法操作数据库,其必须以来pymsql等第三方插件,Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:
"""
MySQL-Python
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
pymysql
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
MySQL-Connector
mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
cx_Oracle
oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
更多:http://docs.sqlalchemy.org/en/latest/dialects/index.html
"""
django中如何反向生成models
python manage.py inspectdb > app/models.py
简单使用
SQLAlchemy只能创建表,删除表,不能在原先的表上在进行修改,如果要进行修改,可以在数据库进行修改,然后再在对应的类上进行修改
执行原生sql(不常用)
import time
import threading
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.engine.base import Engine
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/test?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
def task(arg):
conn = engine.raw_connection()
cursor = conn.cursor()
cursor.execute(
"select * from app01_book"
)
result = cursor.fetchall()
print(result)
cursor.close()
conn.close()
for i in range(20):
t = threading.Thread(target=task, args=(i,))
t.start()
orm使用(重点)
连接
from sqlalchemy import create_engine
create_engine()
返回一个Engine的实例,并且它表示通过数据库语法处理细节的核心接口,在这种情况下,数据库语法将会被解释称Python的类方法
engine = create_engine('mysql+pymysql://root:123456@localhost:3306/test',echo=True)
连接 echo参数为True时,会显示每条执行的sql语句
engine = create_engine('mysql+pymysql://root:123456@localhost:3306/test')
声明映像
- 通过使用Declarative方法,我们可以创建一些包含描述要被映射的实际数据库表的准则的映射类。
- 使用Declarative方法定义的映射类依据一个基类,这个基类是维系类和数据表关系的目录——我们所说的Declarative base class。在一个普通的模块入口中,应用通常只需要有一个base的实例。我们通过declarative_base()功能创建一个基类:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
有了这个Base,我们可以依据这个base定义任意数量的映射类:
class User(Base):
__tablename__ = 'users' # 数据库表名称
id = Column(Integer, primary_key=True) # id 主键
name = Column(String(32), index=True, nullable=False) # name列,索引,不可为空
# email = Column(String(32), unique=True)
#datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
# ctime = Column(DateTime, default=datetime.datetime.now)
# extra = Column(Text, nullable=True)
__table_args__ = (
# UniqueConstraint('id', 'name', name='uix_id_name'), #联合唯一
# Index('ix_id_name', 'name', 'email'), #索引
)
注意: 用Declarative 构造的一个类至少需要一个tablename属性,一个主键行。
生成表
SQLAlchemy不能通过类似于与django的makemigerations
和migerate
自动生成表,需要我们自己进行表的生成
def init_db():
"""
根据类创建数据库表
:return:
"""
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Base.metadata.create_all(engine)
更改表字段
SQLAlchemy不支持在表创建完成后,再进行表里面的字段进行修改,增加,删除,所以如果要进行表的字段修改,有两种方法:
- 手动修改数据库,然后再在对应的类上进行字段的修改
- 删除表,然后修改字段后,再创建表
删除表
def drop_db():
"""
根据类删除数据库表
:return:
"""
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Base.metadata.drop_all(engine)
完整代码
import datetime
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
Base = declarative_base()
class Users(Base):
__tablename__ = 'users' # 数据库表名称
id = Column(Integer, primary_key=True) # id 主键
name = Column(String(32), index=True, nullable=False) # name列,索引,不可为空
age = Column(Integer, default=0)
# email = Column(String(32), unique=True)
#datetime.datetime.now不能加括号,加了括号,以后永远是当前时间
# ctime = Column(DateTime, default=datetime.datetime.now)
# extra = Column(Text, nullable=True)
__table_args__ = (
# UniqueConstraint('id', 'name', name='uix_id_name'), #联合唯一
# Index('ix_id_name', 'name', 'email'), #索引
)
def init_db():
"""
根据类创建数据库表
:return:
"""
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Base.metadata.create_all(engine)
def drop_db():
"""
根据类删除数据库表
:return:
"""
engine = create_engine(
"mysql+pymysql://root:123456@127.0.0.1:3306/aaa?charset=utf8",
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 池中没有线程最多等待的时间,否则报错
pool_recycle=-1 # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
Base.metadata.drop_all(engine)
if __name__ == '__main__':
# drop_db()
init_db()
常用数据类型
数据类型 | 说明 |
---|---|
Integer | 整形,映射到数据库中是int类型。 |
Float | 浮点类型,映射到数据库中是float类型。他占据的32位。 |
Double | 双精度浮点类型,映射到数据库中是double类型,占据64位。 |
String | 可变字符类型,映射到数据库中是varchar类型. |
Boolean | 布尔类型,映射到数据库中的是tinyint类型。 |
DECIMAL | 定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个 类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。 |
Enum | 枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。在ORM模型中,使用Enum来作为枚举 |
Date | 存储时间,只能存储年月日。映射到数据库中是date类型。在Python代码中,可以使用datetime.date 来指定 |
DateTime | 存储时间,可以存储年月日时分秒毫秒等。映射到数据库中也是datetime类型。在Python代码中, 可以使用 datetime.datetime 来指定。 |
Time | 存储时间,可以存储时分秒。映射到数据库中也是time类型。在Python代码中,可以使用datetime.time 来至此那个。 |
Text | 存储长字符串。一般可以存储6W多个字符。如果超出了这个范围,可以使用LONGTEXT类型。映射到数据库中就是text类型。 |
LONGTEXT | 长文本类型,映射到数据库中是longtext类型。 |
Column常用参数
参数 | 详情 |
---|---|
default | 默认值 |
nullable | 是否为空 |
primary_key | 主键 |
unique | 是否唯一 |
autoincrement | 是否自增 |
onupdate | 更新时执行的 |
name | 数据库映射后的属性 |
index | 是否建立索引 |
常用操作(CURD)
创建映射类的实例
user1 = User(name='hades', age=18)
user2 = User(name='bonnie', age=16)
创建会话Session
准备好和数据库会话了,ORM通过Session与数据库建立连接的
当应用第一次载入时,我们定义一个Session类(声明Create_engine()的同时),这个Session类为新的Session对象提供工厂服务。
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
这个定制的Session类会创建绑定到数据库的Session对象。如果需要和数据库建立连接,只需要实例化一个session对象
session =Session()
虽然上面的Session已经和数据库引擎Engine关联,但是还没有打开任何连接。当它第一次被使用时,就会从Engine维护的一个连接池中检索是否存在连接,如果存在便会保持连接知道我们提交所有更改并且/或者关闭session对象。
增加add()/add_all()
# 增加一个
session.add(user1)
session.add(user2)
# 增加多个,可以增加不同的映射实例
# session.add_all([user1, user2, Hosts(ip='127.0.0.1')])
提交commit()
至此,我们可以认为,新添加的这个对象实例还在等待着;user1对象现在并不代表数据库中的一行数据。直到使用flush进程,Session才会让SQL保持连接。如果查询这条数据的话,所有等待信息会被第一时间刷新,查询结果也会立即发行。
- 通过commit()可以提交所有剩余的更改到数据库。
- 注意:提交、查询都会执行所有的等待信息。
- 所有的增加,修改,删除都需要commit提交
session.commit()
回滚rollback()
session.rollback()
查询(重点)
通过Session的query()方法创建一个查询对象。这个函数的参数数量是可变的,参数可以是任何类或者类的描述集合
下面是一个迭代输出User类的例子:
查询第一个
session.query(Users).filter_by(name='lqz').first()
排序
session.query(User).order_by(User.id).all()
# desc(): 降序,一定要加()
session.query(User).order_by(User.id.desc()).all()
# asc():升序
session.query(User).order_by(Users.name.desc(),User.id.asc()).all()
Query也支持ORM描述作为参数。任何时候,多个类的实体或者是基于列的实体表达都可以作为query()函数的参数,返回类型是元组:
session.query(User.name,User.fullname)
session.query(User,User.name).all()
起别名
- 字段起别名:
label()
相当于row.name
session.query(User.name.label("name_label")).all()
- 表起别名:
aliased()
from sqlalchemy.orm import aliased
user_alias = aliased(User,name='user_alias')
session.query(user_alias,user_alias.name).all()
Query 的基本操作包括LIMIT和OFFSET,使用python数组切片和ORDERBY结合可以让操作变得很方便。
限制,用于分页,区间
只查询第二条和第三条数据
session.query(User).order_by(User.id)[1:3]
过滤
使用关键字变量过滤查询结果,filter 和filter_by都使用
- filter传的是表达式,filter_by传的是参数
session.query(User).filter(User.name=='hades').all()
session.query(User).filter_by(name='bonnie').all()
filter与filter_by的区别:
- filter:可以使用> < 等,但是列必须是: 表.列, filter的等于号是==
- filter:不支持组合查询
- filter_by: 可以直接写列,不支持< > filter_by 等于是==
- filter_by 可以支持组合查询
过滤方法
-
equals
session.query(User).filter(User.name == 'ed')
-
not equals
session.query(User).filter(User.name != 'ed')
-
like
session.query(User).filter(User.name.like('%ed%'))
-
in
query.filter(User.name.in_(['ed','wendy','jack'])) # 子查询 session.query(User).filter(User.name.in_(session.query(User.name).filter(User.name.like('%ed%'))
-
not in
query.filter(~User.name.in_('ed','wendy','jack'))
-
is null
session.query(User).filter(User.name == None)
-
is not null
session.query(User).filter(User.name != None)
-
and
session.query(Users).filter(and_(User.name =='ed',User.fullname =='Ed Jones')) # and session.query(Users).filter(User.name == 'ed',User.fullname =='Ed Jones') # and session.query(Users).filter(User.name == 'ed').filter(User.fullname == 'Ed Jones')# and
-
or
query.filter(or_(User.name='ed', User.name='wendy'))
-
占位符查找
#:value 和:name 相当于占位符,用params传参数 session.query(Users).filter(text("id<:value and name=:name")).params(value=224, name='fred').order_by(Users.id).all()
自定义查询sql
session.query(Users).from_statement(text("SELECT * FROM users where name=:name")).params(name='ed').all()
统计计数
count = session.query(User).filter(User.name.like("%t%")).count()
分组
session.query(func.count(User.name),User.name).group_by(User.name)
having
having作为分组的筛选条件
session.query(func.min(User.id), func.avg(User.id)).group_by(Users.name).having(func.min(Users.id) >2).all()
聚合函数
-
func.count
:统计行的数量,和count作用一样fc=session.query(func.count(User.name),User.name).group_by(User.name).all()
-
func.avg
:求平均值fc=session.query(func.avg(User.age),User.name).group_by(User.name).all()
-
func.max
:求最大值fc=session.query(func.max(User.age),User.name).group_by(User.name).all()
-
func.min
:求最小值fc=session.query(func.min(User.age),User.name).group_by(User.name).all()
-
func.sum
:求和fc=session.query(func.sum(User.age),User.name).group_by(User.name).all()
修改
-
第一种:先查询出对象,然后再赋予对象字段新的值
obj = session.query(User).filter(User.name=='hades').first() obj.age = 27 session.commit() # 一定要提交
-
第二种:update()方法,需要传入一个字典
session.query(User).filter(User.name=='hades').update({'age':27}) session.commit() # 一定要提交
-
第三种:在原先的基础上增加,类似于django中的F查询
比如:年龄加1岁
注意:后面必须配合synchronize_session
- 字符串:
synchronize_session=False
- 数字类型:
synchronize_session=evaluata
session.query(User).filter(User.id > 0).update({User.name: User.name + "099"}, synchronize_session=False) # session.query(User).filter(User.id > 0).update({"age": User.age + 1}, synchronize_session="evaluate") # session.commit()
- 字符串:
删除delete()
session.query(Users).filter(Users.id > 4).delete()
session.commit()
基于scoped_session实现线程安全
之前我们直接实例化一个sessionmaker
,绑定引擎,获取Session
对象,然后再实例化,此时会存在一个问题?
场景:如果同时有多个人操作一个表,因为只有全局一个session对象,当某天一个人提交的时候,其他人还没操作完成,此时就会出现线程安全问题
为了解决这个问题,所以就有了scoped_session,将Session
类进行了二次封装,并不是继承,但是确拥有Session所有方法。
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session
from models import Users
engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/s6", max_overflow=0, pool_size=5)
Session = sessionmaker(bind=engine)
"""
# 线程安全,基于本地线程实现每个线程用同一个session
# 特殊的:scoped_session中有原来方法的Session中的一下方法:
public_methods = (
'__contains__', '__iter__', 'add', 'add_all', 'begin', 'begin_nested',
'close', 'commit', 'connection', 'delete', 'execute', 'expire',
'expire_all', 'expunge', 'expunge_all', 'flush', 'get_bind',
'is_modified', 'bulk_save_objects', 'bulk_insert_mappings',
'bulk_update_mappings',
'merge', 'query', 'refresh', 'rollback',
'scalar'
)
"""
#scoped_session类并没有继承Session,但是却又它的所有方法
session = scoped_session(Session)
# ############# 执行ORM操作 #############
obj1 = Users(name="alex1")
session.add(obj1)
# 提交事务
session.commit()
# 关闭session
session.close()