Python3+SQLAlchemy+Sqlite3实现ORM教程


Posted in Python onFebruary 16, 2021

一、Sqlite3、SQLAlchemy安装

Sqlite3是Python3标准库不需要另外安装,只需要安装SQLAlchemy即可。本文sqlalchemy版本为1.2.12

pip install sqlalchemy

Python3+SQLAlchemy+Sqlite3实现ORM教程

二、ORM操作

除了第一步创建引擎时连接URL不一样,其他操作其他mysql等数据库和sqlite都是差不多的。

2.1 创建数据库连接格式说明

sqlite创建数据库连接就是创建数据库,而其他mysql等应该是需要数据库已存在才能创建数据库连接;建立数据库连接本文中有时会称为建立数据库引擎。

2.1.1 sqlite创建数据库连接

以相对路径形式,在当前目录下创建数据库格式如下:

# sqlite://<nohostname>/<path>
# where <path> is relative:
engine = create_engine('sqlite:///foo.db')

以绝对路径形式创建数据库,格式如下:

#Unix/Mac - 4 initial slashes in total
engine = create_engine('sqlite:////absolute/path/to/foo.db')
#Windows
engine = create_engine('sqlite:///C:\\path\\to\\foo.db')
#Windows alternative using raw string
engine = create_engine(r'sqlite:///C:\path\to\foo.db')

sqlite可以创建内存数据库(其他数据库不可以),格式如下:

# format 1
engine = create_engine('sqlite://')
# format 2
engine = create_engine('sqlite:///:memory:', echo=True)

2.1.2 其他数据库创建数据库连接

PostgreSQL:

# default
engine = create_engine('postgresql://scott:tiger@localhost/mydatabase')
# psycopg2
engine = create_engine('postgresql+psycopg2://scott:tiger@localhost/mydatabase')
# pg8000
engine = create_engine('postgresql+pg8000://scott:tiger@localhost/mydatabase')

MySQL:

# default
engine = create_engine('mysql://scott:tiger@localhost/foo')
# mysql-python
engine = create_engine('mysql+mysqldb://scott:tiger@localhost/foo')
# MySQL-connector-python
engine = create_engine('mysql+mysqlconnector://scott:tiger@localhost/foo')
# OurSQL
engine = create_engine('mysql+oursql://scott:tiger@localhost/foo')

Oracle:

engine = create_engine('oracle://scott:tiger@127.0.0.1:1521/sidname')
engine = create_engine('oracle+cx_oracle://scott:tiger@tnsname')

MSSQL:

# pyodbc
engine = create_engine('mssql+pyodbc://scott:tiger@mydsn')
# pymssql
engine = create_engine('mssql+pymssql://scott:tiger@hostname:port/dbname')

2.2 创建数据库连接

我们以在当前目录下创建foo.db为例,后续各步同使用此数据库。

在create_engine中我们多加了两样东西,一个是echo=Ture,一个是check_same_thread=False。

echo=Ture----echo默认为False,表示不打印执行的SQL语句等较详细的执行信息,改为Ture表示让其打印。

check_same_thread=False----sqlite默认建立的对象只能让建立该对象的线程使用,而sqlalchemy是多线程的所以我们需要指定check_same_thread=False来让建立的对象任意线程都可使用。否则不时就会报错:sqlalchemy.exc.ProgrammingError: (sqlite3.ProgrammingError) SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 35608 and this is thread id 34024. [SQL: 'SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password \nFROM users \nWHERE users.name = ?\n LIMIT ? OFFSET ?'] [parameters: [{}]] (Background on this error at: http://sqlalche.me/e/f405)

from sqlalchemy import create_engine

engine = create_engine('sqlite:///foo.db?check_same_thread=False', echo=True)

Python3+SQLAlchemy+Sqlite3实现ORM教程

2.3 定义映射

先建立基本映射类,后边真正的映射类都要继承它

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

Python3+SQLAlchemy+Sqlite3实现ORM教程

然后创建真正的映射类,我们这里以一下User映射类为例,我们设置它映射到users表。

首先要明确,ORM中一般情况下表是不需要先存在的反而为了类与表对应无误借助通过映射类来创建;当然表已戏存在了也无可以,在下一小结中你可以自己决定如果表存在时要如何操作是重新创建还是使用已有表,但使用已有表你需要确保和类的变量名与表的各字段名要对得上。

from sqlalchemy import Column, Integer, String

# 定义映射类User,其继承上一步创建的Base
class User(Base):
  # 指定本类映射到users表
  __tablename__ = 'users'
  # 如果有多个类指向同一张表,那么在后边的类需要把extend_existing设为True,表示在已有列基础上进行扩展
  # 或者换句话说,sqlalchemy允许类是表的字集
  # __table_args__ = {'extend_existing': True}
  # 如果表在同一个数据库服务(datebase)的不同数据库中(schema),可使用schema参数进一步指定数据库
  # __table_args__ = {'schema': 'test_database'}
  
  # 各变量名一定要与表的各字段名一样,因为相同的名字是他们之间的唯一关联关系
  # 从语法上说,各变量类型和表的类型可以不完全一致,如表字段是String(64),但我就定义成String(32)
  # 但为了避免造成不必要的错误,变量的类型和其对应的表的字段的类型还是要相一致
  # sqlalchemy强制要求必须要有主键字段不然会报错,如果要映射一张已存在且没有主键的表,那么可行的做法是将所有字段都设为primary_key=True
  # 不要看随便将一个非主键字段设为primary_key,然后似乎就没报错就能使用了,sqlalchemy在接收到查询结果后还会自己根据主键进行一次去重
  # 指定id映射到id字段; id字段为整型,为主键,自动增长(其实整型主键默认就自动增长)
  id = Column(Integer, primary_key=True, autoincrement=True)
  # 指定name映射到name字段; name字段为字符串类形,
  name = Column(String(20))
  fullname = Column(String(32))
  password = Column(String(32))

  # __repr__方法用于输出该类的对象被print()时输出的字符串,如果不想写可以不写
  def __repr__(self):
    return "<User(name='%s', fullname='%s', password='%s')>" % (
          self.name, self.fullname, self.password)

Python3+SQLAlchemy+Sqlite3实现ORM教程

在上面的定义我__tablename__属性是写死的,但有时我们可能想通过外部给类传递表名,此时可以通过以下变通的方法来实现:

def get_dynamic_table_name_class(table_name):
  # 定义一个内部类
  class TestModel(Base):
    # 给表名赋值
    __tablename__ = table_name
    __table_args__ = {'extend_existing': True}

    username = Column(String(32), primary_key=True)
    password = Column(String(32))
  # 把动态设置表名的类返回去
  return TestModel

2.4 创建数据表

# 查看映射对应的表
User.__table__

# 创建数据表。一方面通过engine来连接数据库,另一方面根据哪些类继承了Base来决定创建哪些表
# checkfirst=True,表示创建表前先检查该表是否存在,如同名表已存在则不再创建。其实默认就是True
Base.metadata.create_all(engine, checkfirst=True)

# 上边的写法会在engine对应的数据库中创建所有继承Base的类对应的表,但很多时候很多只是用来则试的或是其他库的
# 此时可以通过tables参数指定方式,指示仅创建哪些表
# Base.metadata.create_all(engine,tables=[Base.metadata.tables['users']],checkfirst=True)
# 在项目中由于model经常在别的文件定义,没主动加载时上边的写法可能写导致报错,可使用下边这种更明确的写法
# User.__table__.create(engine, checkfirst=True)

# 另外我们说这一步的作用是创建表,当我们已经确定表已经在数据库中存在时,我完可以跳过这一步
# 针对已存放有关键数据的表,或大家共用的表,直接不写这创建代码更让人心里踏实

Python3+SQLAlchemy+Sqlite3实现ORM教程

从上边的讨论可以知道,我们可以定义model然后根据model来创建数据表(当然也可以不创建),那可不可以反过来根据已有的表来自动生成model代码呢,答案是可以的,使用sqlacodegen。

sqlacodegen安装操作如下:

# 如果网络通,直接pip安装
pip install sqlacodegen

# 如果网络不通,先在网络通的机器上使用pip下载sqlacodegen及期依赖包
pip download sqlacodegen
# 上传到真正要安装的机器后再用pip安装,依赖包也会自动安装。版本可能会变化改成自己具体的包名
pip install sqlacodegen-2.1.0-py2.py3-none-any.whl

sqlacodegen生成model操作如下:

# linux应该被安装在/usr/local/bin/sqlacodegen
# mysql+pymysql示例
# 可使用--tables指定要生成model的表,不指定时为所有表都生成model
# 可使用--outfile指定代码输出到的文件,不指定时输出到stdout
# 注意只有当表有主键时sqlacodegen才生成如下的class,不然会使用旧的生成Table()类实例的形式
# 更多说明可使用-h参看
sqlacodegen mysql+pymysql://user:password@localhost/dbname [--tables table_name1,table_name2] [--outfile model.py]

如我的一个示例操作如下,成功为指定表生成model:

Python3+SQLAlchemy+Sqlite3实现ORM教程

2.5 建立会话

增查改删(CRUD)操作需要使用session进行操作

from sqlalchemy.orm import sessionmaker

# engine是2.2中创建的连接
Session = sessionmaker(bind=engine)

# 创建Session类实例
session = Session()

Python3+SQLAlchemy+Sqlite3实现ORM教程

2.6 增(向users表中插入记录)

# 创建User类实例
ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')

# 将该实例插入到users表
session.add(ed_user)

# 一次插入多条记录形式
session.add_all(
  [User(name='wendy', fullname='Wendy Williams', password='foobar'),
  User(name='mary', fullname='Mary Contrary', password='xxg527'),
  User(name='fred', fullname='Fred Flinstone', password='blah')]
)

# 当前更改只是在session中,需要使用commit确认更改才会写入数据库
session.commit()

Python3+SQLAlchemy+Sqlite3实现ORM教程

2.7 查(查询users表中的记录)

2.7.1 查实现

query将转成select xxx from xxx部分,filter/filter_by将转成where部分,limit/order by/group by分别对应limit()/order_by()/group_by()方法。这句话非常的重要,理解后你将大量减少sql这么写那在sqlalchemy该怎么写的疑惑。

filter_by相当于where部分,外另可用filter。他们的区别是filter_by参数写法类似sql形式,filter参数为python形式。

更多匹配写法见:https://docs.sqlalchemy.org/en/13/orm/tutorial.html#common-filter-operators

our_user = session.query(User).filter_by(name='ed').first()

our_user

# 比较ed_user与查询到的our_user是否为同一条记录
ed_user is our_user

# 只获取指定字段
# 但要注意如果只获取部分字段,那么返回的就是元组而不是对象了
# session.query(User.name).filter_by(name='ed').all()
# like查询
# session.query(User).filter(User.name.like("ed%")).all()
# 正则查询
# session.query(User).filter(User.name.op("regexp")("^ed")).all()
# 统计数量
# session.query(User).filter(User.name.like("ed%")).count()
# 调用数据库内置函数
# 以count()为例,都是直接func.func_name()这种格式,func_name与数据库内的写法保持一致
# from sqlalchemy import func
# session.query(func.count(User3.name)).one()
# 字段名为字符串形式
# column_name = "name"
# session.query(User).filter(User3.__table__.columns[column_name].like("ed%")).all()
# 获取执行的sql语句
# 获取记录数的方法有all()/one()/first()等几个方法,如果没加这些方法,得到的只是一个将要执行的sql对象,并没真正提交执行
# from sqlalchemy.dialects import mysql
# sql_obj = session.query(User).filter_by(name='ed')
# sql_command = sql_obj.statement.compile(dialect=mysql.dialect(), compile_kwargs={"literal_binds": True})
# sql_result = sql_obj.all()

Python3+SQLAlchemy+Sqlite3实现ORM教程

另外要注意该链接Common Filter Operators节中形如equals的query.filter(User.name == 'ed'),在真正使用时都得改成session.query(User).filter(User.name == 'ed')形式,不然只后看到报错“NameError: name 'query' is not defined”。

2.7.2 参数传递问题

我们上边的sql直接是our_user = session.query(User).filter_by(name='ed').first()形式,但到实际中时User部分和name=‘ed'这部分是通过参数传过来的,使用参数传递时就要注意以下两个问题。

首先,是参数不要使用引号括起来。比如如下形式是错误的(使用引号),将报错sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column

table_and_column_name = "User"
filter = "name='ed'"

our_user = session.query(table_and_column_name).filter_by(filter).first()

其次,对于有等号参数需要变换形式。如下去掉了引号,对table_and_column_name没问题,但filter = (name='ed')这种写法在python是不允许的

table_and_column_name = User
# 下面这条语句不符合语法
filter = (name='ed')

our_user = session.query(table_and_column_name).filter_by(filter).first()

对参数中带等号的这种形式,现在能想到的只有使用filter代替filter_by,即将sql语句中的=号转变为python语句中的==。正确写法如下:

table_and_column_name = User
filter = (User.name=='ed')

our_user = session.query(table_and_column_name).filter(filter).first()

2.8 改(修改users表中的记录)

# 要修改需要先将记录查出来
mod_user = session.query(User).filter_by(name='ed').first()

# 将ed用户的密码修改为modify_paswd
mod_user.password = 'modify_passwd'

# 确认修改
session.commit()

# 但是上边的操作,先查询再修改相当于执行了两条语句,和我们印象中的update不一致
# 可直接使用下边的写法,传给服务端的就是update语句
# session.query(User).filter_by(name='ed').update({User.password: 'modify_passwd'})
# session.commit()
# 以同schema的一张表更新另一张表的写法
# 在跨表的update/delete等函数中synchronize_session=False一定要有不然报错
# session.query(User).filter_by(User.name=User1.name).update({User.password: User2.password}, synchronize_session=False)
# 以一schema的表更新另一schema的表的写法
# 写法与同一schema的一样,只是定义model时需要使用__table_args__ = {'schema': 'test_database'}等形式指定表对应的schema

Python3+SQLAlchemy+Sqlite3实现ORM教程

2.9 删(删除users表中的记录)

# 要删除需要先将记录查出来
del_user = session.query(User).filter_by(name='ed').first()

# 打印一下,确认未删除前记录存在
del_user

# 将ed用户记录删除
session.delete(del_user)

# 确认删除
session.commit()

# 遍历查看,已无ed用户记录
for user in session.query(User):
  print(user)

# 但上边的写法,先查询再删除,相当于给mysql服务端发了两条语句,和我们印象中的delete语句不一致
# 可直接使用下边的写法,传给服务端的就是delete语句
# session.query(User).filter_by(name='ed').first().delete()

Python3+SQLAlchemy+Sqlite3实现ORM教程

2.10 直接执行SQL语句

虽然使用框架规定形式可以在一定程度上解决各数据库的SQL差异,比如获取前两条记录各数据库形式如下。

# mssql/access
select top 2 * from table_name;

# mysql
select * from table_name limit 2;

# oracle
select * from table_name where rownum <= 2;

但框架存消除各数据库SQL差异的同时会引入各框架CRUD的差异,而开发人员往往就有一定的SQL基础,如果一个框架强制用户只能使用其规定的CRUD形式那反而增加用户的学习成本,这个框架注定不能成为成功的框架。直接地执行SQL而不是使用框架设定的CRUD虽然不是一种被鼓励的操作但也不应被视为一种见不得人的行为。

# 正常的SQL语句
sql = "select * from users"

# sqlalchemy使用execute方法直接执行SQL
records = session.execute(sql)

更多关于Python3 SQLAlchemy Sqlite3相关教程请查看下面的相关链接

Python 相关文章推荐
python中sets模块的用法实例
Sep 30 Python
Python基础入门之seed()方法的使用
May 15 Python
深入解析Python中的list列表及其切片和迭代操作
Mar 13 Python
Python编程实现使用线性回归预测数据
Dec 07 Python
Python中使用Counter进行字典创建以及key数量统计的方法
Jul 06 Python
Python多进程原理与用法分析
Aug 21 Python
cProfile Python性能分析工具使用详解
Jul 22 Python
Pycharm连接远程服务器并实现远程调试的实现
Aug 02 Python
Python FtpLib模块应用操作详解
Dec 12 Python
在TensorFlow中屏蔽warning的方式
Feb 04 Python
Python 实现PS滤镜的旋涡特效
Dec 03 Python
详解Python 3.10 中的新功能和变化
Apr 28 Python
Python3+Appium安装及Appium模拟微信登录方法详解
Feb 16 #Python
Python3使用Selenium获取session和token方法详解
Feb 16 #Python
Python3+PyCharm+Django+Django REST framework配置与简单开发教程
Feb 16 #Python
PyCharm+Miniconda3安装配置教程详解
Feb 16 #Python
Python3+Django get/post请求实现教程详解
Feb 16 #Python
Python3+Flask安装使用教程详解
Feb 16 #Python
Python基于爬虫实现全网搜索并下载音乐
Feb 14 #Python
You might like
php操作mysql数据库的基本类代码
2014/02/25 PHP
PHP+ajaxfileupload+jcrop插件完美实现头像上传剪裁
2014/06/09 PHP
PHP中返回引用类型的方法
2015/04/03 PHP
Zend Framework教程之响应对象的封装Zend_Controller_Response实例详解
2016/03/07 PHP
CodeIgniter生成静态页的方法
2016/05/17 PHP
PHP弱类型的安全问题详细总结
2016/09/25 PHP
PHP+Redis链表解决高并发下商品超卖问题(实现原理及步骤)
2020/08/03 PHP
在网站上应该用的30个jQuery插件整理
2011/11/03 Javascript
setTimeout的延时为0时多个浏览器的区别
2012/05/23 Javascript
键盘KeyCode值列表汇总
2013/11/26 Javascript
jquery创建表格(自动增加表格)代码分享
2013/12/25 Javascript
js函数在frame中的相互调用详解
2014/03/03 Javascript
jQuery的事件委托实例分析
2015/07/15 Javascript
JS拖拽组件学习使用
2016/01/19 Javascript
vue.js入门教程之计算属性
2016/09/01 Javascript
浅谈javascript中的 “ &amp;&amp; ” 和 “ || ”
2017/02/02 Javascript
JavaScript脚本语言是什么_动力节点Java学院整理
2017/06/26 Javascript
详解js静态资源文件请求的处理
2017/08/01 Javascript
Angular学习教程之RouterLink花式跳转
2018/05/03 Javascript
js实现各浏览器全屏代码实例
2018/07/03 Javascript
JavaScript实现图片轮播特效
2019/10/23 Javascript
vue quill editor 使用富文本添加上传音频功能
2020/01/14 Javascript
微信小程序pinker组件使用实现自动相减日期
2020/05/07 Javascript
js和jquery判断数据类型的4种方法总结
2020/08/28 jQuery
[04:38]完美世界携手游戏风云打造 卡尔工作室饰品系统篇
2013/04/25 DOTA
Python实现完整的事务操作示例
2017/06/20 Python
Python的多维空数组赋值方法
2018/04/13 Python
修改默认的pip版本为对应python2.7的方法
2018/11/06 Python
Python 基于FIR实现Hilbert滤波器求信号包络详解
2020/02/26 Python
总经理任命书
2014/03/29 职场文书
超市开业庆典策划方案
2014/05/14 职场文书
火灾现场处置方案
2014/05/28 职场文书
驻村工作先进事迹
2014/08/14 职场文书
未婚证明书模板
2014/10/08 职场文书
2014年检验员工作总结
2014/11/19 职场文书
公务员保密工作承诺书
2015/05/04 职场文书