day38_仿优酷系统之ORM架构
1、ORM架构介绍
ORM: 对象关系映射 ---> 映射到数据库MySQL中的数据表
类名 ---> 表名
对象 ---> 一条记录
对象.属性 ---> 字段
模拟Django的ORM
**优点:**
1、可跨平台
2、可使用对象.属性方式存取值
3、无需关心具体的sql语句
**缺点:**
1、高级的多层封装导致执行效率低
2、长时间使用可能会忘记sql语句
2、ORM架构实现
1、定义字段类
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
class IntegerField(Field):
'''整形类'''
def __init__(self,name,column_type='int',primary_key=False,default=0):
super().__init__(name,column_type,primary_key,default)
class StringField(Field):
'''字符串类'''
def __init__(self,name,column_type='varchar(64)',primary_key=False,default=None):
super().__init__(name,column_type,primary_key,default)
2、自定义元类
class OrmMetaclass(type):
'''
1、控制表的主键存在且唯一
2、设置表名为类名
3、
'''
def __new__(cls, class_name, class_base, class_dict):
'''使用自定义元类定义类时,传过来的参数就是:类,类名,基类,类的名称空间'''
# 除了数据表,其他类原路返回,不做控制
if class_name == 'Models':
return type.__new__(cls, class_name, class_base, class_dict)
# 确定表名,并添加到类的名称空间
table_name = class_dict.get('table_name', class_name)
class_dict['table_name'] = table_name
# 设定一个专门的字典,存放字段对象,并添加到名称空间中
mappings = {}
class_dict['mappings'] = mappings
# 1、确定主键存在且唯一
primary_key = None
for key, value in class_dict.items():
# 判断value是不是一个字段
if not isinstance(value, Field):
continue
mappings[key] = value # 将字段键值对单独存放到一个字典,那么取值时,只需要访问该字典即可,不会受到其他项的干扰
if not value.primary_key:
continue
if primary_key:
print(f'调试信息1 primary_key:{primary_key}')
raise TypeError('主键必须唯一!')
primary_key = key
print(f'调试信息2 primary_key:{primary_key}')
if not primary_key:
raise TypeError('主键必须有!')
# 将主键名添加到名称空间
class_dict['primary_key'] = primary_key
# 删除名称空间中重复的键值对(字段对象)
for key in mappings.keys():
class_dict.pop(key)
print(f'调试信息 class_dict:{class_dict}')
return type.__new__(cls, class_name, class_base, class_dict)
3、定义表类,即通过自定义元类创建表
class Models(dict,metaclass=OrmMetaclass):
'''
1、继承dict类,字段名为key,字段对象为值
2、字典可以接收任意多个值,即关键字参数
3、使用魔法方法,将字典值的存取方式转化为对象值的存取方式
'''
def __getattr__(self, item):
# 对象.属性 不存在该属性时自动触发
return self.get(item)
def __setattr__(self, key, value):
# 在对象.key=value 时,对象没有该属性,自动触发
self[key] = value
@classmethod
def orm_select(cls, **kwargs):
'''
1、查一条记录的所有信息,因此筛选项都为*
2、kwargs 为字典,是sql语句的过滤条件,此处默认只有where关键字'''
# 连接数据库
mysql = MySQLClient()
if not kwargs:
sql = 'select * from %s' % cls.table_name
res = mysql.my_select(sql)
return [cls(**item) for item in res]
# 设定sql语句
sql = 'select * from %s where %s'
# 处理kwargs参数,拼接sql语句
keys = []
values = []
for key, value in kwargs.items():
keys.append(key + '=?')
values.append(value)
sql = sql % (cls.table_name, ' and '.join(keys))
# 将?替换为占位符,使用execute方法避免sql注入问题
sql = sql.replace('?', '%s')
print(f'调试信息 122 sql:{sql}')
res = mysql.my_select(sql, values)
return [cls(**item) for item in res]
def orm_insert(self, **kwargs):
if not kwargs:
raise ValueError('正确的插入语句:字段名=值')
# 连接数据库
mysql = MySQLClient()
# 处理kwargs字典,拼接sql语句
keys = []
values = []
args = []
for key, value in kwargs.items():
keys.append(key)
values.append(value)
args.append('?')
# field_str = str(keys).strip('[]') # 不能使用这个方法,会把字符串的分号带进去
# args_str = str(args).strip('[]')
field_str = ','.join(keys)
args_str = ','.join(args)
sql = 'insert into %s (%s) values(%s)' % (self.table_name, field_str, args_str)
print(f'调试消息 sql:{sql}')
# 将?替换为占位符,使用execute避免sql注入
sql = sql.replace('?', '%s')
res = mysql.my_execute(sql, values)
return res
def orm_update(self):
'''更新条件固定为主键'''
mysql = MySQLClient()
keys = []
values = []
for key, value in self.mappings.items():
if value.primary_key:
primary_key = value.name + '=' + str(getattr(self, value.name))
print(f'调试信息121 primary_key:{primary_key}')
else:
keys.append(value.name + '=?')
values.append(getattr(self, value.name))
key_value_str = ','.join(keys)
sql = 'update %s set %s where %s' % (self.table_name, key_value_str, primary_key)
print(f'调试信息128 sql:{sql}')
sql = sql.replace('?', '%s')
res = mysql.my_execute(sql, values)
return res
def orm_delete(self, kwargs):
pass
class User(Models):
# 用户表类
user_id = IntegerField(name='user_id', primary_key=True)
user_name = StringField(name='user_name')
password = StringField(name='password')
class Movie(Models):
# 电影信息表类
movie_id = IntegerField(name='movie_id', primary_key=True)
movie_name = StringField(name='movie_name')
# 使用
if __name__ == '__main__':
user1 = User()
# 查
# res = user1.orm_select()
# print(res)
# # 增
# res2 = user1.orm_insert()
# print(res2)
# # 改
# ## 先查要改的记录信息,加载到内存
# user2 = User.orm_select(user_id=2)[0] # 返回的是列表套字典
# print(user2)
# ## 再将字典转化成表记录,即本例中的可以使用 对象.属性 存取值的字典
# user2 = User(**user2)
# ## 再修改并更新
# user2.user_name = '牛大力'
# user2.orm_update()
# mysql_client.py
import pymysql
class MySQLClient:
def __init__(self):
self.conn = pymysql.connect(
host='localhost',
user='root',
password='',
database='test_orm',
charset='utf8')
self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
def my_select(self, sql, value=None):
self.cursor.execute(sql, value)
res = self.cursor.fetchall()
return res
def my_execute(self, sql, values):
try:
print(sql)
self.cursor.execute(sql, values)
self.conn.commit()
return '操作成功'
except Exception as e:
print(e)
def close(self):
self.cursor.close()
self.cursor.close()