ORM对象关系映射
ORM
ORM对象关系映射:将对象映射成数据表中的一条条记录
类 映射为 ---> 数据表名
对象 ---> 数据记录(一条条数据 比如: "张三 18岁 男")
对象.属性 ---> 数据字段(一条条数据中的具体数据 比如: "男" 或者 "18岁" 或者 "张三")
# 演示映射关系(伪代码)
"""比如User表中有名字,年龄,性别:
User表:
名字、 年龄、 性别
apple、80、female
class User: # User为表名
pass
user_obj = User() # 获取记录
user_obj.name属性 # 获取user表中的name数据信息
user_obj.age属性 = 80 # 给user表添加数据
"""
# 字段类型的定义,每个字段都应该有 字段名、字段类型、是否为主键、是否有默认值
# 字段类型父类class Field:
def__init__(self, name,
column_type,
primary_key=False, default=None):
self.name = name
self.column_type = column_type
self.primary_key = primary_key
self.default = default
# varchar字段类型
class StringField(Field):
def__init__(self, name,
column_type="varchar(64)",
primary_key=False, default=None):
super().__init__(name, column_type, primary_key, default)
# int字段类型
class IntegerField(Field):
def__init__(self, name,
column_type="int",
primary_key=False, default=0):
super().__init__(name, column_type, primary_key, default)
# Models类
# 问题1:每一张表类,都必须定义一个__init__方法,但是无法确定每张表的字段个数以及字段名字
# 解决1:通过继承dict字典类,让用户输入所需要添加的字段以及字段名字,解决该问题
# 问题2:继承的字典无法通过 "对象.属性" 的取值方式来取值,也无法通过 "对象.属性=属性值" 的方式来赋值
# 解决2:__getattr__方法解决了取值方式,__setattr__方法解决了赋值方式
# 1.让Models类继承dict字典类,所有子类继承Models就等于继承了dict类class Models(dict):
# 2.在 "对象.属性" 获取属性时,若 "没有该属性" 时触发 __getattr__
def__getattr__(self, item):
# print(item)
return self.get(item)
# 2.当 "对象.属性 = 属性值" ,"添加或修改属性" 时触发 __setattr__
def__setattr__(self, key, value):
self[key] = value
# 假如User表中有 用户名和密码 两个字段
class User(Models):
pass
if__name__ == "__main__":
# print(dict(username="apple", password="123")) # {"username": "apple", "password": "123"}
# User类继承了dict类,相当于 User(username="apple", password="123") == dict(username="apple", password="123")
user_obj = User(username="apple", password="123")
print(user_obj)
# print(res.get("username")) # 字典的取值方式一
# print(res["username"]) # 字典的取值方式二
print(user_obj.username) # 字典的取值方式三,也是我们需要实现的取值方式 ---> " user_obj.name属性 # 获取user表中的name数据信息 "
print("添加属性前: ---> ", user_obj)
# User类实例化出来的 user_obj 普通对象 添加属性的方式
user_obj.user_type = "admin"# 实现通过 对象.属性=属性值 添加数据 ---> " user_obj.age属性 = 80 # 给user表添加数据 "
print(user_obj.user_type)
print("添加属性后: ---> ", user_obj) # 验证是否将 user_type="admin" 添加到名称空间中
执行结果:
{"username": "apple", "password": "123"}apple
添加属性前:
---> {"username": "apple", "password": "123"}admin
添加属性后:
---> {"username": "apple", "password": "123", "user_type": "admin"}
ORM的作用(两点)
第一个作用:控制表类的创建,也就是使用元类控制表创建的过程
第二个作用:给表类封装 查、改(增、改、删)的方法,MySQL类的封装
一、元类控制表类的创建
字段类型的定义 + Model类 + 元类
# 创建表的三个注意点:
1.保证一张表必须要有表名字
2.保证一张表中只能有一个主键
3.将所有 "字段名"与"字段对象" 添加到一个独立的字典(mappings)中,以key(字段名):value(字段对象) 添加到类的名称空间中,方便后期使用
# 字段类型父类class Field:
def__init__(self, name, column_type, primary_key, default):
self.name = name
self.column_type = column_type
self.primary_key = primary_key
self.default = default
# varchar字段类型
class StringField(Field):
def__init__(self, name, column_type="varchar(64)", primary_key=False, default=None):
super().__init__(name, column_type, primary_key, default)
# int字段类型
class IntegerField(Field):
def__init__(self, name, column_type="varchar(64)", primary_key=False, default=0):
super().__init__(name, column_type, primary_key, default)
# 自定义OrmMetaclass元类:控制类的创建过程
class OrmMetaclass(type):
# __call__:先调用__new__,只要定义类就会触发__new__
def__new__(cls, class_name, class_bases, class_dict): # class_name:类名 class_bases:基类 class_dict:类的名称空间
# print(f"类名:{class_name}")
# print(f"基类:{class_bases}")
# print(f"类的名称空间:{class_dict}")
# 过滤掉Models类,我们限制的是用户创建表的过程(可以说是用户创建表的规范),所以在此处过滤掉Models类,因为我们不限制Models类
if class_name == "Models":
return type.__new__(cls, class_name, class_bases, class_dict)
# 第一个注意点:保证表必须要有表名。 获取table表名,若自定义则获取、没有则默认使用类名
# dict.get(key, class_name) key若有返回对应的值,若没有则返回默认值class_name就是默认值
table_name = class_dict.get("table_name", class_name) # 获取类名称空间的自定义表名table_name,若没有则默认以类名为表名
print(table_name) # userinfo,若未定义表名,那么表名为类名,此处即User
# 自定义一个主键值标识,后面做逻辑判断使用
# 主键值:主键名为 字段名
primary_key = None # 比如主键是id,那么id字段就是主键值的名字(primary_key = id)
# 第三个注意点:定义一个字典用来存放 "字段名"与"字段对象"
mappings = {}
print(class_dict) # 打印类中的名称空间
"""
执行结果:
{"__module__": "__main__",
"__qualname__": "User",
"table_name": "userinfo",
"id": <__main__.IntegerField object at 0x000000000287F1C8>,
"username": <__main__.StringField object at 0x000000000287F088>,
"password": <__main__.StringField object at 0x000000000287F388>}
上面class_dict的名称空间中,我们需要的仅仅是后面的id、username、password键值对属性,前两个不需要,我们需要将其剔除,后面将剔除后的结果重新放进mappings字典中
"""
# 使用for循环将类中的名称空间字典 迭代依次取出
for k, v in class_dict.items(): # k为字段名,v为字段对象
# 将类中的名称空间无需的属性进行剔除筛选
# isinstance():判断一个对象是否是另一个类的实例
if isinstance(v, Field): # id、username、password的value都是Field的实例
# print(k, v)
# print(v.__dict__) # {"name": "password", "column_type": "varchar(64)", "primary_key": False, "default": None}
# 第三个注意点:将所有的 "字段名" 与 "字段对象" 添加到一个定义好的独立的字典(mappings)中
mappings[k] = v
"""
# 此处有一个坑:
# class_attr.pop(k) # 这里当字典被迭代时,不能修改其属性
"""
# 第二个注意点:保证一张表只能有一个唯一的主键
# 判断字段对象如果有主键 primary_key,则为primary_key变量赋值
if v.primary_key: # 如果建的表中有主键,执行下一步,若没有不执行下一步
# 若第二次执行到此处,primary_key有值,证明有主键,抛出异常
if primary_key:
raise TypeError("一张表只能有一个主键!")
primary_key = v.name # 将自定义的主键值标识修改为 字段对象中的名字(和字段名一样)
# 给类的名称空间 添加table_name、primary_key、mappings属性
class_dict["table_name"] = table_name # 将表名添加到类的名称空间中
class_dict["primary_key"] = primary_key # 将主键添加到类的名称空间中
class_dict["mappings"] = mappings # 将存放 "字段名"与"字段对象"的字典添加到类的名称空间中
print(class_dict)
"""
执行结果:
{"__module__": "__main__",
"__qualname__": "User",
"table_name": "userinfo",
"id": <__main__.IntegerField object at 0x000000000237F1C8>,
"username": <__main__.StringField object at 0x000000000237F088>,
"password": <__main__.StringField object at 0x000000000237F388>,
"table_name": "User",
"primary_key": "id",
"mappings": {"id": <__main__.IntegerField object at 0x000000000237F1C8>,
"username": <__main__.StringField object at 0x000000000237F088>,
"password": <__main__.StringField object at 0x000000000237F388>}}
上面class_dict的名称空间中,有重复的"字段名"与"字段对象",在此将class_dict中重复的"字段名"与"字段对象"过滤掉
"""
# 过滤掉类名称空间中重复的字段属性
for key in mappings.keys(): # 根据mappings中的字段属性来过滤掉class_dict中的字段属性,因为mappings中的字段属性在class_dict中的字段属性都存在
class_dict.pop(key)
print(class_dict)
"""
执行结果:
{"__module__": "__main__",
"__qualname__": "User",
"table_name": "userinfo",
"primary_key": "id",
"mappings": {"id": <__main__.IntegerField object at 0x000000000283F1C8>,
"username": <__main__.StringField object at 0x000000000283F088>,
"password": <__main__.StringField object at 0x000000000283F388>}}
过滤完成!
"""
# 若建立的表中所有字段都没有主键(规定每一张表中都需要有一个主键),抛出异常
ifnot primary_key:
raise TypeError("表中必须要有一个主键!")
return type.__new__(cls, class_name, class_bases, class_dict)
# 让Models类继承dict字典类,所有子类继承Models就等于继承了dict类
class Models(dict, metaclass=OrmMetaclass):
# 在 "对象.属性" 获取属性时,若 "没有该属性" 时触发 __getattr__
def__getattr__(self, item):
# print(item)
return self.get(item)
# 当 "对象.属性 = 属性值" ,"添加或修改属性" 时触发 __setattr__
def__setattr__(self, key, value):
self[key] = value
# 假如User表中有 id、用户名和密码 三个字段
# 问题3:一个表中只能有一个唯一的主键,在当前表类中,无法控制用户定义类的行为
class User(Models):
table_name = "userinfo"# 自定义一个表名,若此处不写,表名默认即为类名User
# 字段类中的name属性必须与User表中类属性同名
id = IntegerField(name="id", primary_key=True)
username = StringField(name="username")
password = StringField(name="password")
测试代码:
(错误演示)第一种:创建字段时不添加主键primary_key
(错误演示)第二种:创建字段时添加多个主键primary_key
(正确演示):创建字段时只添加一个主键primary_key
二、MySQL类的封装
给表类封装 查、改(增、改、删)的方法
mysql_setting.py:编写代码
import pymysql# MySQL连接类class MySQL:
__instance = None
@classmethod
def Singleton(cls):
if cls.__instance:
cls.__instance = cls()
return cls.__instance
# 实例化MySQL类时,获取数据库连接对象,获取游标对象
def__init__(self):
self.client = pymysql.connect(
host="127.0.0.1",
port=3306,
user="root",
passwd="123",
db="data",
charset="utf8",
autocommit=True
)
# 获取游标
self.cursor = self.client.cursor(pymysql.cursors.DictCursor)
# 自定义查询数据方法
def select(self, sql, args=None):
# 1.先提交查询sql语句
# sql = "select * from table"
# sql = "select * from table where id=%s"
self.cursor.execute(sql, args) # 假如查询语句中有where条件,则需要传入参数(避免sql注入,在execute中传参)
# 2.获取返回的查询结果
res = self.cursor.fetchall()
for data in res:
print(data)
# return res
# 自定义提交修改数据方法(添加、更新、删除)
def execute(self, sql, args=None):
# 提交sql语句
# sql = "insert into table(id, username) values(%s, %s)"
try:
self.cursor.execute(sql, args) # 此处一定会传入参数
except Exception as e:
print(e)
# 关闭游标、关闭连接
def close(self):
self.cursor.close()
self.client.close()
mysql_test.py:测试代码
from day38.db_control.mysql_setting import MySQL # 导入写好的MySQL模块if__name__ == "__main__":
# 查询数据
def select_data(sql, args=None):
mysql_obj = MySQL()
# sql = "select * from userinfo where id=%s"
mysql_obj.select(sql, args)
print(id(mysql_obj))
# 插入数据
def execute_data(sql, args=None):
mysql_obj = MySQL()
mysql_obj.execute(sql, args)
print(id(mysql_obj))
# 调用查询数据:查询插入数据前的数据
select_data("select * from userinfo")
"""
执行结果:
{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}
{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}
{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}
1990595350992
"""
# 插入数据
execute_data("insert into userinfo(username, password, gender, sex, phone_number) values(%s, %s, %s, %s, %s)",
("owen", "321456", "三年三班", "female", "15244444444"))
"""
执行结果:
1990595350992
"""
# 调用查询数据:查询插入数据后的数据
select_data("select * from userinfo")
"""
执行结果:
{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}
{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}
{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}
{"id": 4, "username": "owen", "password": "321456", "gender": "三年三班", "sex": "female", "phone_number": 15244444444}
1990595350992
"""
# 更新数据
execute_data("update userinfo set username=%s where username=%s", ("lucy", "owen"))
"""
执行结果:
1990595350992
"""
# 调用查询数据:查询更新数据后的数据
select_data("select * from userinfo")
"""
执行结果:
{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}
{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}
{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}
{"id": 4, "username": "lucy", "password": "321456", "gender": "三年三班", "sex": "female", "phone_number": 15244444444}
1990595350992
"""
# 删除数据
execute_data("delete from userinfo where username=%s", "lucy")
"""
执行结果:
1990595350992
"""
# 调用查询数据:查询删除数据后的数据
select_data("select * from userinfo")
"""
执行结果:
{"id": 1, "username": "tom", "password": "123456", "gender": "三年四班", "sex": "male", "phone_number": 13511111111}
{"id": 2, "username": "jack", "password": "456789", "gender": "三年一班", "sex": "male", "phone_number": 13622222222}
{"id": 3, "username": "jerry", "password": "147258", "gender": "三年二班", "sex": "male", "phone_number": 13733333333}
1990595350992
"""
以上是 ORM对象关系映射 的全部内容, 来源链接: utcz.com/z/538098.html