共计 5630 个字符,预计需要花费 15 分钟才能阅读完成。
ORM: Object Relational Mapper.
目前 Python 有很多 ORM 工具可以将数据库映像为 Python 的 Objects 对象。其中比较知名的有 Django 的 ORM,SQLAlchemy, PostgreSQL 等。SQLAlchemy 有更多的人维护,功能也比较齐全。所以一般是我们的首选项。
对于 SQLAlchemy 的使用者来说,只要你一开始连接上数据库,不管是 Sqlite,MySQL 还是什么,后面的处理方式完全一样。这种便利性也是它受欢迎的原因。
抛弃了传统的自己编织 SQL 语句、制作模型、连接数据库方式,SQLAlchemy 直接把这些东西全包在黑盒里面,让我们完全不需要去管。连 SQL-Injection 注入这种东西也被它帮忙防范了。这样一来,可以说在连接数据库方面,帮我们节省了最少一半以上的代码。
甚至连数据查询,SQLAlchemy 也代替了 SQL 语句,而使用了专门的类似 MongoDB 的 Object.query.filter_by(name=’Jason’).all()这种方法。
安装:
# 安装 sqlalchemy
$ pip install sqlalchemy
安装 Drivers:
# Sqlite
# 不需要,Python 自带
# MySQL
$ pip install pymysql
# Postgresql
$ pip install psycopg2
SQLAlchemy 自身不带数据库 driver,需要我们自己安装,并在连接时候指定。而这些 driver,实际上就是我们曾经手动连接数据库所用的包。而 SQLAlchemy 只是代替我们使用这些同样的包。
连接数据库
创建一个 sqlite 的 ORM 引擎:
from sqlalchemy import create_engine
# 连接格式为:sqlite://<Hostname>/<path>
engine = create_engine(‘sqlite:///foo.db’, echo=True)
创建一个 MySQL 的 ORM 引擎:
from sqlalchemy import create_engine
# 连接格式为:dialect+driver://username:password@host:port/database
engine = create_engine(‘mysql+pymysql://root:password123@localhost/db_test_01’, echo=True)
数据库的位置(三斜杠为相对路径,四斜杠为绝对路径):
# 使用绝对路径的数据库文件(////),如 /tmp/mydatabase.db
engine = create_engine(‘sqlite:////tmp/mydatabase.db’)
# 使用当前「执行位置」数据库文件(/// 或 ///./)
engine = create_engine(‘sqlite:///mydatabase.db’)
# 使用当前「执行位置」父级目录 (///../) 的数据库文件
engine = create_engine(‘sqlite:///../mydatabase.db’)
# 使用当前「脚本位置」的数据库文件
import os
cwd = os.path.split(os.path.realpath(__file__))[0]
engine = create_engine(‘sqlite:///{}/mydatabase.db’.format(cwd))
Create Tables 创建表
注意:不同于 SQL 语句,SQLAlchemy 中的表名是完全区分大小写的!
创建一个 Schema 表(指单纯表,不包含 ORM 对象):
from sqlalchemy import create_engine, MetaData
from sqlalchemy import Table, Column
from sqlalchemy import Integer, String, ForeignKey
engine = create_engine(‘mysql+pymysql://root:password123@localhost/db_test_01’, echo=True)
metadata = MetaData(engine)
# 创建一个表
user_table = Table(‘tb_user’, metadata,
Column(‘id’, Integer, primary_key=True),
Column(‘name’, String(50)),
Column(‘fullname’, String(100))
)
# 让改动生效
metadata.create_all()
创建一个 ORM 对象(包括表):
# 导入表格创建引擎
from sqlalchemy import create_engine
# 导入列格式
from sqlalchemy import Column, Integer, String, ForeignKey
# 导入创建 ORM 模型相关
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = ‘tb_Person’
id = Column(‘id’, Integer, primary_key=True)
username = Column(‘username’, String, unique=True)
engine = create_engine(‘sqlite:///test.sqlite’, echo=True)
Base.metadata.create_all(bind=engine)
用普通表 Table 和 ORM 对象创建的表有什么不同?他们在数据库中创建的,是完全相同的表!唯一区别是,Table 创建的不包含 ORM 对象,也就是不提供让你直接操作 Python 对象的功能。这么做的好处是,有很多只是关联作用的表,没有必要生成 ORM 对象。
删除数据库中的表
# engine = …
# Base = …
# 逐个 ORM 对象删除对应的表,如 User 类
User.__table__.drop(engine)
# 删除全部表
Base.metadata.drop_all(engine)
设计或调试过程中,我们经常要频繁改动表格,所以有必要在创建表格前把测试数据库中的表都清除掉,再创建新的定义。
Insertion 插入数据
将数据添加到数据库:
# …
# 导入 session 相关(用于添加数据)
from sqlalchemy.orm import sessionmaker, relationship
user = User()
user.id = 1
user.username = ‘Jason’
Session = sessionmaker(bind=engine)
session = Session()
session.add(user)
session.commit()
session.close()
注意:这里的 session 和网站上的 session 概念有点不一样。这里是用来 commit 提交数据库变动的工具。
批量添加数据(向 add_all()传入列表):
session.add_all([user1, user2, user3] )
添加每条数据的时候自动 flush():
session = sessionmaker(bind=engine, autoflush=True)
autoflush 是在每次 session.add()自动执行 session.flush(),即在插入数据库之前就在内存中生成所有对象的动态数据(如主键 ID 等)。一般默认是选 false,因为会影响效率。最好是需要的时候,才手动执行 session.flush()
具体缘由,看下一节“数据生效”。
Take effect 数据生效
SQLAlchemy 中的 create_all()和 session.commit()都是直接让 python 文件中定义的对象在数据库中生效的语句。在此之前,无论怎么定义,数据都是在内存中,而没有在数据库中的。
注意区分:
create_all 只是让创建表格结构生效,无关 insert 的数据条目
session.commit()只是让添加的数据生效,而不负责任何表格结构。
这两个的顺序,当然是先创建表格,再插入数据。
只是,如果我们知道了这个原理,在编码中才能比较运用自由。比如,连 create_engine()创建引擎,我们都可以在后面定义,而没必要非得写在文件头,即所有的 ORM 定义之前。create_engine 只要定义在所有 ORM 类和 Schema 表之后即可。
此后,我们再开始进行数据插入工作,也就利用到了 session。
session 过程中呢,我们也会遇到互相引用主键外键 ID 的情况。但是注意,这时候因为还没有使用最终的 session.commit()真正提交数据到数据库中,这些 ID 是没有值的。解决办法就是利用内置的方法 session.flush(),将 session 中已添加的所有对象填充好数据,但是这时候还没有提交到数据库,只是我们内部可以正常访问各种 ID 了。
更新/删除数据
更新:
# Get a row of data
me = session.query(User).filter_by(username=’Jason’).first()
# Method 1:
me.age += 1
session.commit()
# Method 2:
session.query().filter(
User.username == ‘Jason’
).update(
{“age”: (User.age +1)}
)
session.commit()
# Method 3:
setattr(user, ‘age’, user.age+1)
session.commit()
Get Primary Key Value 获取主键值
#sqlalchemy can’t get primary key , #sqlalchemy 如何获得主键的值
这个问题花了我很多时间探索查询,不得其解,才明白原来是很显然的事。
参考思否:SQLAlchemy 中返回新插入数据的 id?
虽然在没有用 session 或 engine 插入数据之前,我们可以直接浏览从 ORM 创建的对象中的属性值。但是这个时候无论如何都获取不到 primar_key 主键列的值。
因为这时候主键还没有插入数据库,作为动态的值,在数据库没生效之前也就为 None。
为什么需要获取 value of primary_key?考虑如下这些场景:
子表中的 foreign key 外键需要引用主表的 id
??
那么该怎么获取主键 ID 呢?
再参考 Stackoverflow:sqlalchemy flush() and get inserted id? 再参考:sqlalchemy 获取插入的 id 再参考:Sqlalchemy; 将主键设置为预先存在的数据库表(不使用 sqlite)
如果要想在插入数据之前就获取主键等动态列的值,那么有这几种方法:
直接利用 SQLAlchemy 建立类直接的内部关联,而不直接使用 ID
主表插入数据,另 session 生效后,再用 query 获取相应对象,来得到它的 ID。
(*) 主表先用 session.add(..),再 session.flush(),然后就可以获取 ID,最后再 session.commit()
不使用 primary key 主键,自己手动创建 ID,这样来随便获取。
推荐做法如下:
即每次新创建对象后,立刻 session.add(..),然后立刻 session.flush(),全部都添加好的文末,再 session.commit().
Query 查询
注意:query 是通过 session 进行的,也就是必须在 session.commit()之后才能进行查询,否则会报错。
这里将的 query 查询,指的都是在插入到数据库生效之后。理解这个很重要,因为在对象未插入到数据库之前,很多主键、外键等内容都是不存在的,也就无法查询到。
参考:pythonsheets – Object Relational basic query
查询数据:
session.commit()
# …
users = session.query(User).all()
# 返回的是多个 User 类的对象:>>> [<User 1>, <User 2>, ….]
for u in users:
print(u.id, u.username)
常用查询方法:
# 获取某 ORM 中数据 .query(ORM 类名)
>>> session.query(User).all() # All rows of data
>>> session.query(User).first() # First row of data as an object
# 查询结果排序 .order_by(类名. 列名)
>>> session.query(User).order_by(User.birth).all()
# 筛选结果 .filter(True/False 表达式)
>>> session.query(User).filter(User.name != ‘Jason’).all()
>>> session.query(User).filter(User.name.like(‘%ed%’) ).all() # Fuzzy search
>>> session.query(User).filter(User.id in [1, 2, 3] ).all() # IN
>>> session.query(User).filter(~ User.id in [4, 5, 6] ).all() # NOT IN
>>> session.query(User).filter(User.school == ‘MIT’, User.age < 24).first() # AND
>>> session.query(User).filter(_or(User.school == ‘MIT’, User.age < 24) ).first() # OR