MySQL ORM
1 MySQL 连接配置
1.1 default 配置
配置数据库连接方式
conf/config.py
DATABASES = {
"default": "mysql+retrypool://root:password@127.0.0.1:3306/test?max_connections=300&stale_timeout=300",
}Butterfly 访问 MySQL 基于 Peewee 库,Peewee 是一个简单小巧的 Python ORM,它非常容易学习,使用起来非常方便。
1.2 自定义配置(仅读取本 region 配置)
如 conf/servicer/db_x1.py 中,增加 url_x1 = "mysql+retrypool://root:password@127.0.0.1:3306/test?max_connections=300&stale_timeout=300"
from xlib import db
from xlib.db import peewee
from xlib.util import config_util
def _get_db(name):
db_url = config_util.get_config(None, "db_x1", name)
db_obj = db.connect(db_url)
return db_obj
class ProcessRecords(peewee.Model):
"""
X1 task process 记录, 用于耗时统计
"""
id = peewee.BigAutoField()
task_id = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")], index=True)
task_batch_id = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
work_flow = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
entity = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
step = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
operation = peewee.CharField(constraints=[peewee.SQL("DEFAULT ''")])
created_at = peewee.DateTimeField(constraints=[peewee.SQL("DEFAULT CURRENT_TIMESTAMP")])
start_at = peewee.DateTimeField(constraints=[peewee.SQL("DEFAULT 0000-00-00 00:00:00")])
cost = peewee.IntegerField(constraints=[peewee.SQL("DEFAULT 0")])
parameters = peewee.TextField(null=True)
err_msg = peewee.TextField(null=True)
class Meta(object):
"""
meta
"""
database = _get_db("url_x1")
table_name = 'process_records'1.3 自定义配置(多 region adapter)
lib_model.py
例子
2 基本知识
编写 model.py
demo
在官方的 Quckstart 中,Peewee 中 Model 类、fields 和 model 实例与数据库的映射关系如下:
Model
class Database table
Field
instance Column on a table
Model
instance Row in a database table
也就是说
一个 Model 类代表一个数据库的表
一个 Field 字段代表数据库中的一个字段
一个 model 类实例化对象则代表数据库中的一行。
Peewee 的实现原理可以结合 道生一,一生二,二生三,三生万物
2.1 字段
字段类用于描述模型属性到数据库字段的映射,每一个字段类型都有一个相应的 SQL 存储类型,如 varchar, int。并且 python 的数据类型和 SQL 存储类型之间的转换是透明的。
在创建模型类时,字段被定义为类属性。有一种特殊类型的字段 ForeignKeyField,可以以更直观的方式表示模型之间的外键关系。
这允许你编写如下的代码:
2.1.1 字段类型表
IntegerField
integer
integer
integer
BigIntegerField
integer
bigint
bigint
SmallIntegerField
integer
smallint
smallint
AutoField
integer
serial
integer
FloatField
real
real
real
DoubleField
real
double precision
double precision
DecimalField
decimal
numeric
numeric
CharField
varchar
varchar
varchar
FixedCharField
char
char
char
TextField
text
text
longtext
BlobField
blob
bytea
blob
BitField
integer
bigint
bigint
BigBitField
blob
bytea
blob
UUIDField
text
uuid
varchar(40)
DateTimeField
datetime
timestamp
datetime
DateField
date
date
date
TimeField
time
time
time
TimestampField
integer
integer
integer
IPField
integer
bigint
bigint
BooleanField
integer
boolean
bool
BareField
untyped
不支持
不支持
ForeignKeyField
integer
integer
integer
2.1.2 字段初始参数
所有字段类型接受的参数与默认值
null = False– 布尔值,表示是否允许存储空值index = False– 布尔值,表示是否在此列上创建索引unique = False– 布尔值,表示是否在此列上创建唯一索引column_name = None– 如果和属性名不同,底层的数据库字段使用这个值default = None– 字段默认值,可以是一个函数,将使用函数返回的值primary_key = False– 布尔值,此字段是否是主键constraints = None- 一个或多个约束的列表 例如:[Check('price > 0')]sequence = None– 序列填充字段(如果后端数据库支持)collation = None– 用于排序字段 / 索引的排序规则unindexed = False– 表示虚拟表上的字段应该是未索引的(仅用于 sqlite)choices = None– 一个可选的迭代器,包含两元数组(value, display)help_text = None– 表示字段的帮助文本verbose_name = None– 表示用户友好的字段名
一些字段的特殊参数
CharField
max_length
FixedCharField
max_length
DateTimeField
formats
DateField
formats
TimeField
formats
TimestampField
resolution, utc
DecimalField
max_digits, decimal_places, auto_round, rounding
ForeignKeyField
model, field, backref, on_delete, on_update, extra
BareField
coerce
2.1.3 字段默认值
创建对象时,peewee 可以为字段提供默认值,例如将字段的默认值null设置为0
如果想提供一个动态值,比如当前时间,可以传入一个函数
数据库还可以提供字段的默认值。虽然 peewee 没有明确提供设置服务器端默认值的 API,但您可以使用 constraints 参数来指定服务器默认值:
2.1.4 外键字段
foreignkeyfield 是一种特殊的字段类型,允许一个模型引用另一个模型。通常外键将包含与其相关的模型的主键(但您可以通过指定一个字段来指定特定的列)。
可以通过追加 _id 的外键字段名称来访问原始外键值
ForeignKeyField 允许将反向引用属性绑定到目标模型。隐含地,这个属性将被命名为 classname_set,其中 classname 是类的小写名称,但可以通过参数覆盖 backref:
2.1.5 日期字段
DateField TimeField 和 DateTimeField 字段
DateField 包含 year month day TimeField 包含 hour minute second DateTimeField 包含以上所有
3 使用说明
而使用过程,分成两步:
定义 Model,建立数据库
操作数据库
3.1 定义 Model,建立数据库
在使用的时候,根据需求先定义好 Model,然后可以通过 create_tables() 创建表,若是已经创建好数据库表了,可以通过 python -m pwiz 脚本工具直接创建 Model。
3.1.1 创建 Model
3.1.1.1 第一种方式
先定义 Model,然后通过 db.create_tables() 创建或 Model.create_table() 创建表。 例如,我们需要建一个 Person 表,里面有 name、birthday 和 is_relative 三个字段,我们定义的 Model 如下:
然后,我们就可以创建表了
其中,CharField、DateField、BooleanField 等这些类型与数据库中的数据类型一一对应,我们直接使用它就行,至于 CharField => varchar(255) 这种转换 Peewee 已经为我们做好了 。
完成之后,就会在数据库中看到 test 数据库中,创建好了 person 表
3.1.1.2 第二种方式
已经存在过数据库,则直接通过 python -m pwiz 批量创建 Model。
例如,上面我已经创建好了 test 库,并且创建了 person 表,表中拥有 id、name、birthday 和 is_relative 字段。那么,我可以使用下面命令:
然后,输入密码,pwiz 脚本会自动创建 Model,内容如下:
3.1.2 Model 定义
3.1.2.1 复合主键约束 (CompositeKey)
3.1.2.2 联合唯一索引 (indexes)
需要注意的是,上面语法,三层元组嵌套, 元组你懂得, 一个元素时需要加个 , 逗号。 别忘了。
3.1.3 设置数据库
使用 Peewee 配置数据库的三种方式
DatabaseProxy
仅当实际数据库驱动程序在运行时变化时才使用 DatabaseProxy
如果只是连接值在运行时发生变化,例如数据库文件的路径或数据库主机,则应改用 Database.init()
故 DatabaseProxy 暂时不需要使用
3.2 操作数据库
操作数据库,就是增、删、改和查。
3.2.1 增 (object.save,Model.create,Model.insert)
批量写数据
3.2.2 删 (object.delete_instance,Model.delete)
删除有两种方式
使用 object.delete_instance
使用 Model.delete
3.2.3 改 (Model.update, object.save)
若是,已经添加过数据的的实例或查询到的数据实例,且表拥有 primary key 时,此时使用 save() 就是修改数据;若是未拥有实例,则使用 update().where() 进行更新数据。
3.2.3.1 peewee 的 update 是原子的
需要注意的是,在使用 update 的时候千万不要在 Python 中使用计算再更新,要使用 SQL 语句来更新,这样才能具有原子性。
错误做法
正确做法
3.2.3.2 update 的几种方法
3.2.3.3 无则插入,有则更新
两种方式
存在更新,不存在则插入 replace 与 on_conflict_replace() 是等效的
MySQL 提供了一种独有的语法 ON DUPLICATE KEY UPDATE 可以使用以下方法实现。
3.2.4 查(单条 Model.get)
3.2.4.1 get
单条数据使用 Person.get() 就行了,也可以使用 Person.select().where().get()。若是查询多条数据,则使用 Person.select().where(),去掉 get() 就行了。语法很直观,select() 就是查询,where 是条件,get 是获取第一条数据。
3.2.4.2 get_or_none
3.2.4.3 get_or_create
Peewee 有一个辅助方法来执行“获取 / 创建”类型的操作: Model.get_or_create() 首先尝试检索匹配的行。如果失败,将创建一个新行。
参数:
返回值:
3.2.4.4 get_by_id
对于主键查找,还可以使用快捷方法 Model.get_by_id()
3.2.4.5 select
使用 Model.select() 查询获取多条数据。select 后可以添加 where 条件,如果不加则查询整个表。
select 代表 sql 语句中 select 后面的语句表示要展示的字段
where 代表 where 条件语句 得到一个数据集合
语法:
参数:
示例:
select() 返回结果是一个 ModelSelect 对象,该对象可迭代、索引、切片。当查询不到结果时,不报错,返回 None。并且 select() 结果是延时返回的。如果想立即执行,可以调用 execute() 方法。
注意
3.2.4.6 获取记录条数 count 方法
使用 .count() 方法可以获取记录条数。
也许你会问,用 len() 方法可以吗?当然也是可以的,但是是一种不可取的方法。
这两者的实现方式天差地远。用 count() 方法,执行的 SQL 语句是:
而用 len() 方法执行的 SQL 语句却是:
直接返回所有记录然后获取长度,这种方法是非常不可取的。
3.2.4.7 排序 order_by 方法
排序默认是升序排列,也可以用 + 或 asc() 来明确表示是升序排列:
用 - 或 desc() 来表示降序:
如要对多个字段进行排序,逗号分隔写就可以了
3.2.4.8 查询条件
当查询条件不止一个,需要使用逻辑运算符连接,而 Python 中的 and、or 在 Peewee 中是不支持的,此时我们需要使用 Peewee 封装好的运算符,如下:
&
and
Person.select().where((Person.Name == '张三') & (Person.Age == 30))
`
`
or
Person.select().where((Person.Name == '张三') | (Person.Age == 30))
~
not
Person.select().where(~Person.Name == '张三')
当条件全为 and 时,也可以用逗号分隔,get 和 select 中都可以
3.2.4.9 支持的比较符
3.2.4.10 如何根据查询项,设置不同的搜索条件
搜索项支持 job_reqid,job_id 等
3.3 查看 ORM 对应的原生 SQL 语句
后缀 .sql() 打印对应原生 sql
3.4 常见查询算子
.in_(value)
在查找中(与 << ),如 model.<field_name>.in_(<field_value>.split(",")
.not_in(value)
不在查找中。
.is_null(is_null)
为空或不为空。接受布尔参数。
.contains(substr)
子字符串的通配符搜索。
.startswith(prefix)
搜索以开头的值 prefix .
.endswith(suffix)
搜索以结尾的值 suffix .
.between(low, high)
在哪里搜索 low <= value <= high 。
.regexp(exp)
正则表达式匹配(区分大小写)。
.iregexp(exp)
正则表达式匹配(不区分大小写)。
.bin_and(value)
二进制和
.bin_or(value)
二进制或
.concat(other)
使用连接两个字符串或对象 || .
.distinct()
为非重复选择标记列。
.collate(collation)
使用给定的排序规则指定列。
.cast(type)
将列的值强制转换为给定的类型。
3.5 一些有用的拓展
3.5.1 模型转换成字典
除了在查询的时候使用 model.dicts 以外,还可以使用 model_to_dict(model) 这个函数。
3.5.2 从数据库生成模型
可以使用 pwiz 工具从已有的数据库产生 peewee 的模型文件
3.5.3 创建自己的 Field
peewee 中创建自己的 Field, 主要通过继承 Field 或其子类来完成,
如果有 mysql 中对应的字段,则将其赋值给 db_field 即可。这里对 set, enum 并没有找到通用的字段定义,但是对具体的业务可以进行如 GenderField 这样的个性化定制。
如果没有,则使用其父类的 db_field 字段,并定义 db_value 和 python_value 两个方法来完成与数据库中数据类型之间的转化
使用
4 实践
4.1 user
ImportError: No module named dateutil //faker 使用
结果:
4.2 School
4.3 fn.FIND_IN_SET
以【星桥】列表为例, 搜索 job_tags 使用FIND_IN_SET 函数(使用 fn 功能),搜索 is_valid 使用 peewee.NodeList 进行拼接
SQL
4.4 fn.SUM
查询统计总量:
使用的 peewee 的话,使用方法是:
说明,: 结果返回的是一个ModelSelect
示例:
结果:
建议使用 result.dicts()[0]
4.5 JOIN.LEFT_OUTER
可以通过 alias 将 table 自定义名
连表操作: query_cmd = query_cmd.join(cluster_model, on=(unit_model.cluster_id == cluster_model.id), attr='cluster_obj', join_type=peewee.JOIN.LEFT_OUTER)
获取连表的 field:
(1) query_cmd = unit_model.select(unit_model, cluster_model)
(2) cluster_obj = getattr(record, 'cluster_obj', None)
4.6 count
4.7 limit
4.8 time 范围
如:
DateTimeField(column_name="c_time", default=datetime.now)
4.9 输出某个字段的最新记录
使用子查询和 join
在上面的代码中,subquery.c.max_time 是子查询中计算的最大时间列。确保在 JOIN 条件中使用的是 max_time,而不是 create_time。
4.10 timestamp 类型
Peewee 的字段类型是逻辑抽象,而非严格绑定
Peewee 的 DateTimeField 是一个逻辑字段,它代表“日期+时间”数据,但具体存储为哪种数据库类型(DATETIME、TIMESTAMP 或 TIMESTAMPTZ)取决于数据库后端:
MySQL/MariaDB:
DateTimeField默认映射到DATETIME,但可以手动改为TIMESTAMP(通过ALTER TABLE或constraints)。
数据库中的类型
peewee 定义
插入数据
Last updated