一、ORM介绍
对象关系映射,把数据库中的表数据(表名、表记录、字段)全部映射到python中。
mysql: python:
表名 ————>类名
记录 ————>对象
字段 ————>对象.属性
第一步:写字段类型的类
--字段名 --字段类型(长度) --varchar(256) --int --是否为主键 --默认值 仿优酷中使用的字段类型: —int —string
第二步:写表类
User: #用户表 user_name pwd Movie: #电影表 movie_name movie_size Notice: #公告表 title content
接下来我们解决三个问题:
问题1: 假设100张表就需要写100个__init__。 解决: 继承一个Models父类 问题2: 每张表的字段名与字段数量不同,导致无法直接继承Models父类 解决: dict是对象,继承dict,触发字典内部的__init__(可接受任意数量以及任意类型属性) 问题3: 字典的取/存值方式有限,希望改为 对象.属性取值, 对象.属性=值 存值的方式。 解决: __getattr__: 取值方式 __setattr__: 存值方式
第三步:表的约束
问题: 让所有的表类都遵循以下约束 - 表名 - 必须要有一个唯一主键 - 表的字段 解决:通过元类去控制类的创建,使其遵循以上约束.
第四步:
- 表名 - 必须要有一个唯一主键 - 表的字段
***************************mysql_control.py****************************** import pymysql class MySQL: #接下来是一个单例模式,因为每定义一个函数都要实例化mysql_control.MySQL() __instance = None #隐藏 def __new__(cls, *args, **kwargs): if not cls.__instance: cls.__instance = object.__new__(cls) return cls.__instance def __init__(self): #创建数据库连接 self.mysql = pymysql.connect( host = '127.0.0.1', port = 3306, user= 'root', password = '12345678', database = 'yjy', charset ='utf8', autocommit = True ) #获取游标 self.cursor = self.mysql.cursor( pymysql.cursors.DictCursor ) #查看 def select(self,sql,args=None): self.cursor.execute(sql,args) #提交select语句 #获取查询结果 res = self.cursor.fetchall() return res #提交 def execute(self,sql,args): try: # insert into table(name, pwd) values('yjy', '123'); self.cursor.execute(sql,args) except Exception as e: print(e) def close(self): self.cursor.close() #关闭游标 self.mysql.close() #关闭连接对象mysql
***************************orm.py****************************** ''' 需求: 创建一张User表,表内字段(id,name,pwd) ''' # class User: #先定义一个User类,即创建User表,orm的特性就是将表名映射成类名 # def __init__(self,id,name,password): # self.id = id # self.name = name # self.password = password import mysql_control '''第一步:写字段类型类''' #父类 # 定义这个父类的关键就在于把各个类中相同的东西拿出来,减小代码冗余 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 # int类型 class IntegerField(Field): #继承父类 def __init__(self,name,column_type= "int",primary_key=False,default=0): super().__init__(name,column_type,primary_key,default) #继承父类中的这些方法 #string varchar() 类型 class StringField(Field): def __init__(self,name,column_type= "varchar(256)",primary_key= False,default = None): super().__init__(name,column_type,primary_key,default) '''第三步,第四步:通过元类进行表的约束''' class OrmMetaClass(type): def __new__(cls, class_name, class_base, class_attr): # 接收Models类的东西、User类的东西 # print(class_name, class_base, class_attr) # (类名、父类、类的名称空间) # print(class_attr) if class_name == "Models": return type.__new__(cls, class_name, class_base, class_attr) # 若不是Models则开始控制类的创建 # 1.表名 若table_name没有,则取class_name table_name = class_attr.get('table_name', class_name) # 表的名字 mappings = {} # 在python中返回的表,它是以;列表套字典的形式 primary_key = None # 2.约束必须有一个唯一的主键 for k, v in class_attr.items(): # print(k, v) # user_id, IntegerField(name='user_id', primary_key=True) if isinstance(v, Field): # 3.获取所有字段,把所有表的字段都封装到一个独立的字典中mappings mappings[k] = v if v.primary_key: # 如果字段有主键 # 判断是否有多个主键 if primary_key: raise TypeError('只能有一个主键') # 给他跑一个异常 primary_key = v.name # 剔除原表类的名称空间中重复的字段属性,目的是为了减少内存的占用 for k in mappings.keys(): class_attr.pop(k) # 如果没有主键 if not primary_key: raise TypeError('必须要有一个主键') class_attr['table_name'] = table_name # select * from self.table_name where id=self.primary_key。 class_attr['primary_key'] = primary_key # insert into self.table_name(字段名, ...) values (字段对象.值) class_attr['mappings'] = mappings # {'字段名': '字段对象'} # print(class_attr) return type.__new__(cls, class_name, class_base, class_attr) # 解决100张表就需要写100个__init__的问题 class Models(dict, metaclass=OrmMetaClass): # 继承dict解决的是每张表的字段名与字段数量不同,导致无法直接继承Models父类 # 继承dict,触发字典内部的__init__(可接受任意数量以及任意类型属性 def __init__(self, **kwargs): # **kwargs就是id,name,pwd 'name': 'yjy' --- > name=yjy super().__init__(**kwargs) def __getattr__(self, item): # 将 字典[key]的取值方式转换为---> 字典.key的取值方式 return self.get(item) def __setattr__(self, key, value): # 将字典的赋值方式转变为对象.属性的方式 self[key] = value # 查看 @classmethod def select(cls, **kwargs): # name= 'yjy',pwd= '123' mysql_obj = mysql_control.MySQL() if not kwargs: # 拼接select查询语句 sql = 'select * from %s' % cls.table_name res = mysql_obj.select(sql) # sql(查询条件):select * from User where name = yjy; else: key = list(kwargs.keys())[0] # data_name value = kwargs.get(key) # yjy # 拼接select查询语句 sql = 'select * from %s where %s =?' % (cls.table_name, key) # 防止sql注入 sql = sql.replace('?', '%s') # 提交sql,并返回查询结果 res = mysql_obj.select(sql, value) # cls(**{key:value}) ---> cls(key=value) --> obj # 把列表套字典 ---> 列表套对象 return [cls(**r) for r in res] # [{}, {}, {}] ---> [obj, obj, obj..] # 插入 def save(self): mysql = mysql_control.MySQL() # sql: insert into table(name, pwd..) values('yjy', '123', ...); # sql: insert into table(name, pwd..) values(?,?,?); # 获取所有的字段名 fields = [] # 他是一个列表,我们需要用','.join() 拼接起来 # 获取字段对应的值 values = [] # 替换条件 replace = [] # [?,?] for k, v in self.mappings.items(): # print(k, v) # user_id = IntegerField(name='user_id', primary_key=True) fields.append(k) # k == v.name values.append(getattr(self, v.name, v.default)) # getattr(User(), v.name) --> 字段值 replace.append('?') # [?, ?, ?...] # insert into user_info(user_id,user_name,pwd) values(?,?,?) sql = 'insert into %s(%s) values(%s)' % (self.table_name, ','.join(fields), ','.join(replace)) sql = sql.replace('?', '%s') # 防止sql注入 mysql.execute(sql, values) # 提交sql语句 # 更新 def sql_update(self): '''默认使用主键当做更新的查询条件''' mysql = mysql_control.MySQL() # 获取字段名 fields = [] # 获取字段名 values = [] # 获取主键的值 primary_key = None # name=yjy, pwd='123' for k, v in self.mappings.items(): if v.primary_key: # 通过反射获取主键的值 primary_key = getattr(self, v.name) else: fields.append(v.name + '=?') # [name=?, pwd=?] # 通过反射获取修改字段的值 values.append( getattr(self, v.name) ) # sql: update user_info set name=yjy, pwd='123' where user_id=1; # sql: update user_info set name=?, pwd=? where user_id=1; sql = 'update %s set %s where %s=%s' % (self.table_name, ','.join(fields), self.primary_key, primary_key) sql = sql.replace('?', '%s') mysql.execute(sql, values) '''第二步:创建表类''' class User(Models): #Models中已经有表的字段,所以在这里直接继承就好了 # 自定义表名 table_name = 'user_info' # {'table_name': 'user_info'} # id, name, pwd user_id = IntegerField(name='user_id',primary_key=True) user_name = StringField(name='user_name') pwd = StringField(name='pwd') #注意:我们要在数据库中创建下面这样一张表 ''' create table user_info( user_id int primary key auto_increment, user_name varchar(256), pwd varchar(256)); ''' class Movie(Models): pass class Notice(Models): pass if __name__ == '__main__': obj = User(user_name='yjy') obj.save() #插入数据 user_obj = User.select(user_name='yjy')[0] #查看数据数据 print(user_obj) ##{'user_id': 1, 'user_name': 'yjy', 'pwd': None} print(user_obj.user_name) #yjy user_obj.user_name = 'yjyyjyyjy' user_obj.sql_update() print(user_obj.user_name) #yjyyjyyjy #数据库中结果 +---------+-----------+------+ | user_id | user_name | pwd | +---------+-----------+------+ | 1 | yjyyjyyjy | NULL | +---------+-----------+------+