zoukankan      html  css  js  c++  java
  • 1107 python自定义实现ORM

    ORM

    存: 从代码通过ORM保存到mysql中,保存的结果是json

    取: 从mysql中通过ORM取出,取出来的也是json

    对象关系映射,
    
    客户端的请求到服务端,服务端接受到指令,使用ORM对数据库进行操作,而数据库的库表都是已经存在的,ORM中操作的指令无非是对应数据库的表字段,通过操作ORM的代码来实现对表,表中字段数据的操作
    
    一开始写的时候就是对象,然后通过ORM保存到数据库中,保存的都是json数据,但想要取出来的也是对象,所以使用元类来进行封装创建
    
    接口层 -- 数据层 -- ORM -- 数据库
    
    			对象关系映射
    	类名		  ---		表名
    	
    	对象	       ---       数据行
    		# 对象 通过类实例化传参得来
    		
    	对象.属性	  ---		字段	
    		# 对象.属性 在类实例化的时候添加字段的属性,触发__init__
    
    创建一个User表,表内有字段(id,name,pwd)
    # 1.表名就是类名User
    # 2.字段:需要在类实例化的时候添加字段(.属性)
    #     实例化会触发__init__方法
    #     2.1 创建字段时具有不同的类型,创建字段类型类
    #             - 字段名
    #             - 字段类型
    #                 int
    #                 varchar
    #             - 是否为主键
    #             - 默认值
    #             定义字段类型类,继承同一父类
    

    模型表类

    创建字段类型类

    # 写字段类型类
    定义字段的约束:字段的名字,字段的类型,字段是否为主键,字段有无默认值
    # 字段父类
    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__(self,name,column_type,primary_key,default)
    # varchar
    class StringFiled(Field):
        def __init__(self,name,column_type = "varchar(255)",primary_key = False,default =None):
            super().__init__(self,name,column_type,primary_key,default)
    

    创建模型表类

    模型表类是一张一张的表,表中有username,pwd等字段用于记录用户的数据

    user表,movie表
    user表中应该有 username,pwd	--- 这是字段
    
    # 创建模型表类
    #     1.创建表类的重复 --- 继承
    #     2.每一张表的字段数量,名字都不一样,无法继承同一父类
    #         --- 父类继承dict,dict是对象,继承dict,触发内部__init__(可接受任意数量已经类型属性)
    #         dict = {key:value}    与  dict(key=value)  结果一致
    #     3.字典的取存值方式有限,改为对象.属性的方式存取值
    #         存, 对象.属性没有时触发    __getattr__
    #         取, 对象.属性调用时触发    __setattr__
    
    class Models(dict):
        # 1.使用字典接受所有的关键字参数
        def __init__(self,**kwargs):
            super().__init__(**kwargs)  # kwargs接受所有的关键字参数
        # 2.取值时使用getattr,self是调用者本身item是没有的属性(key)
        def __getattr__(self, item):
            return self.get(item)
        # 3.存值时使用setattr,将调用者本身的字典中的key赋值保存为value
        def __setattr__(self, key, value):
            self[key] = value
    
    
    
    class User(Models):
        # def __init__(self,id,name,pwd):
        #     self.id = id
        #     self.name = name
        #     self.pwd = pwd
        pass
    
    class Movie(Models):
        # def __init__(self,id,name,pwd):
        #     self.id = id
        #     self.name = name
        #     self.pwd = pwd
        pass
    
    
    if __name__ == '__main__':
        d1=dict(name = 1)   # {'name': 1}
        d2 = User(name='222')   #{'name': '222'}
        print(d1)
        # print(d2.name)    # 报错
        # 取值
        print(d2['name'])   # 222
        print(d2.name)      # 222
        # 存值
        d2['name']=1231
        print(d2)       #{'name': 1231}
        d2.pwd = 1231
        print(d2)       #{'name': 1231, 'pwd': 1231}
    

    表的约束

    // 问题:让所有的表类都遵循约束,防止创建传值的失误
    	- 表名
    	- 必须有唯一主键
    	- 表的字段
    解决:	使用元类去控制类的创建,使其遵循规则
    	元类控制:
            - 表名
            - 必须有唯一主键
            - 表的字段
    

    元类的封装

    # 写字段类型类
    
    # 字段父类
    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)
    # varchar
    class StringFiled(Field):
        def __init__(self,name,column_type = "varchar(255)",primary_key = False,default=None):
            super().__init__(name,column_type,primary_key,default)
    
    
    # 4.元类的创建
    class MyMetaclass(type):
        # 子类的方法覆盖父类的__init方法
        # 控制了类的定义
        def __new__(cls,class_name,class_base,class_dict):
            # 接收models类的,与user类的所有
            # print(args)    # (类名,父类,类的名称空间)
            # print(kwargs)   空
            # print(class_name)
            # print(class_base)
            # print(class_dict)
            # 类的所有东西
            '''
            Models
            (<class 'dict'>,)
            {'__module__': '__main__', '__qualname__': 'Models', '__init__': <function Models.__init__ at 0x0000022B6753EBF8>, '__getattr__': <function Models.__getattr__ at 0x0000022B6753EC80>, '__setattr__': <function Models.__setattr__ at 0x0000022B6753ED08>, '__classcell__': <cell at 0x0000022B674E91C8: empty>}
            
            User
            (<class '__main__.Models'>,)
            {'__module__': '__main__', '__qualname__': 'User', 'table_name': 'user_info', 'user_id': <__main__.IntegerField object at 0x0000022B674EEDA0>, 'user_name': <__main__.StringFiled object at 0x0000022B674EEF28>, 'pwd': <__main__.StringFiled object at 0x0000022B674EE048>}
    
            '''
            # 1.剔除models类
            if class_name == 'Models':
                # 是models类就原路返回,什么也不做
                return type.__new__(cls, class_name, class_base, class_dict)
            # 2.设定一张表必须有一个表名
                # 从类的名称空间中获取table_name的值,如果没有就以类名作为表名
            table_name = class_dict.get('table_name',class_name)
            # print(table_name)   # user_info
            # 3.主键名默认为空
            primary_key = None
            # 4.定义一个空字典,用于存放所有的 字段 对象
            mappings = {}
                # 遍历名称空间
            for k,v in class_dict.items():
                # print(k,v)
                '''
                __module__ __main__
                __qualname__ User
                想要的是以下字段
                table_name user_info
                user_id <__main__.IntegerField object at 0x00000287AB9BED68>
                user_name <__main__.StringFiled object at 0x00000287AB9BEEF0>
                pwd <__main__.StringFiled object at 0x00000287AB9BEF28>'''
                # 过滤字段,获取想要的字段
                if isinstance(v,Field):
                    # 给字典进行赋值保存字段
                    mappings[k] = v
                    # print(v)
                    ''' v 就是每个字段对象
                    <__main__.IntegerField object at 0x000001E6B993EDD8>
                    <__main__.StringFiled object at 0x000001E6B993EE10>
                    <__main__.StringFiled object at 0x000001E6B993EF98>'''
            # print(mappings)
                    ''' 字典中字段对应的值都是对象.
                    {'user_id': <__main__.IntegerField object at 0x000002056198E048>, 'user_name': <__main__.StringFiled object at 0x000002056198EEF0>, 'pwd': <__main__.StringFiled object at 0x000002056198EF28>}'''
                    # 5.判断主键是否是唯一
                    #     先判断字段对象中是否存在主键,对象.属性的方法获取primary_key
                    if v.primary_key:
                        # 如果存在判断标记主键是否有值,有值说明循环过一次了
                        if primary_key:
                            raise TypeError('只能有一个主键')
                        # 主键标识不存在,则给primary_key赋值,防止重复
                        primary_key = v.name    # 赋值为当前具有主键的字段名
    
            # 6.节省资源,将mappings字典中和名称空间的重复元素删除
            for k in mappings.keys():
                class_dict.pop(k)
    
    
            # 判断是否为主键
            if not primary_key:
                raise TypeError('必须有一个主键')
    
            # 给类的名称空间中添加表名
            class_dict['table_name'] = table_name
            # 给类的名称空间中添加主键名
            class_dict['primary_key'] = primary_key
            # 给类的名称空间添加mappings字典,字典中是所有字段以及对象
            class_dict['mappings'] = mappings
    
            return type.__new__(cls,class_name,class_base,class_dict)
    
    
    
    
    
    
    # 创建模型表类
    #     1.创建表类的重复 --- 继承
    #     2.每一张表的字段数量,名字都不一样,无法继承同一父类
    #         --- 父类继承dict,dict是对象,继承dict,触发内部__init__(可接受任意数量已经类型属性)
    #         dict = {key:value}    与  dict(key=value)  结果一致
    #     3.字典的取存值方式有限,改为对象.属性的方式存取值
    #         存, 对象.属性没有时触发    __getattr__
    #         取, 对象.属性调用时触发    __setattr__
    
    
    class Models(dict,metaclass=MyMetaclass):
        # 1.使用字典接受所有的关键字参数
        def __init__(self,**kwargs):
            super().__init__(**kwargs)  # kwargs接受所有的关键字参数
        # 2.取值时使用getattr,self是调用者本身item是没有的属性(key)
        def __getattr__(self, item):
            return self.get(item)
        # 3.存值时使用setattr,将调用者本身的字典中的key赋值保存为value
        def __setattr__(self, key, value):
            self[key] = value
    
    
    
    class User(Models):
        # 自定义表名
        table_name = 'user_info'
        # 定义字段
        user_id = IntegerField(name='user_id',primary_key=True)
        user_name = StringFiled(name='user_name')
        pwd = StringFiled(name='pwd')
    
    
    if __name__ == '__main__':
        s=User()
        print(s.mappings)
        '''
        {'user_id': <__main__.IntegerField object at 0x0000013DFA6BEDA0>, 'user_name': <__main__.StringFiled object at 0x0000013DFA6BEDD8>, 'pwd': <__main__.StringFiled object at 0x0000013DFA6BEF60>}'''
        print(s.table_name) # user_info
        print(s.primary_key)    # user_id
    

    数据的操作

    pymysql的使用
    
    '''pymysql的连接使用'''
    import pymysql
    
    class MySQLClient:
        # 1.创建连接
        def __init__(self):
            # 连接客户端
            self.client = pymysql.connect(
                host = '127.0.0.1',
                port = 3306,
                user = 'root',
                password = '',
                database = 'youku',
                charset = 'utf8',
                autocommit = True   # 自动增长
            )
            # 创建游标对象并设置自动提交
            # 类似于原来的
            #     conn = pymysql.connect(user='root', password='', database='oldboy')
            #     cursor = conn.cursor()
            self.cursor = self.client.cursor(
                pymysql.cursors.DictCursor      # 输出字典格式
            )
    
        # 2.定义提交查询sql命令
        def my_select(self,sql,value=None):
            # 提交查询的sql指令
            self.cursor.execute(sql,value)
            # 获取查询以后得结果
            res = self.cursor.fetchall()
            return res
    
        # 3.封装'提交sql命令,插入或者更新'的方法
        def my_execute(self,sql,values):
            try:
                self.cursor.execute(sql,values)
            except Exception as e:
                print(e)
    
        # 4.关闭数据库
        def close(self):
            self.cursor.close()
            self.client.close()
    
    

    select查询语句

    #     查看    定义为类方法,可以类这调用
        @classmethod
        def select(cls,**kwargs):  #**kwargs接受所有的关键字参数
            # print(kwargs)   # 打散关键字参数 {'name': 'tank'}
            # 将MySQLClient实例化得到对象
            mysql = MySQLClient()
            # 定义sql语句: select * from 表名 where 条件
            # 判断传入的关键字参数是否有值
            if not kwargs:
                # 1.不存在,直接查询所有
                sql = 'select * from %s' % cls.table_name
                    # 调用MySQLClient接口函数查询数据
                res = mysql.my_select(sql)
    
            else:
                # 2.存在,按条件查询 sql: select * from User where id=1;
                key = list(kwargs.keys())[0]    # 返回的是一个对象,需要转list
                # print(kwargs.keys())  # dict_keys(['name'])
                # print(list((kwargs.keys())))  # ['name']
                value = kwargs.get(key)
                # 防止sql注入问题,用户输入的都用?替代
                sql = 'select * from %s where %s=?' % (cls.table_name,key)
                sql = sql.replace('?','%s')     # 将?替换成%s 在execute中传参防止注入
                # 将sql语句与条件values传入查询函数方法
                res = mysql.my_select(sql,value)
                # print(res)
                # 将res列表中的字典调用cls自己本身,将其转变为对象
            return [cls(**d) for d in res]
        
    运行----------------------------------
    
    if __name__ == '__main__':
        # 查
        # 将select设置为类的绑定方法,可直接类调用
        res = User.select(user_name = 'tank')
        print(res)  # [{'user_id': 1, 'user_name': 'tank', ' pwd': '123'}]
        print(res[0])   # {'user_id': 1, 'user_name': 'tank', ' pwd': '123'}
        print(res[0].user_name)     # tank
        
        
        res = User.select()
            print(res)      # [{'user_id': 1, 'user_name': 'tank', ' pwd': '123'}, {'user_id': 2, 'user_name': 'nick', ' pwd': '321'}]
    
    

    insert 插入数据

     # 插入数据
        def orm_insert(self):
            mysql = MySQLClient()
            # sql = 'insert into 表名(字段名) values (值1,值2);
    
            # 存储字段名
            keys = []
            # 存储字段对应的值,用于传参
            values = []
            # 存储问号,有几个?就存储几个
            args = []
            # print(self.mappings)
            '''{'user_id': <__main__.IntegerField object at 0x000001789E2600F0>, 'user_name': <__main__.StringFiled object at 0x000001789E2602E8>, 'pwd': <__main__.StringFiled object at 0x000001789E260320>}'''
            for k,v in self.mappings.items():
                # print(1,k,v)
                '''1 user_id    <__main__.IntegerField object at 0x000001B9D25EE128>
                 1 user_name    <__main__.StringFiled object at 0x000001B9D25EE320>
                 1 pwd <__main__.StringFiled object at 0x000001ECAE8DF320>'''
                # 过滤掉主键,主键是自增的所以不会传值
                # print(v.primary_key)    # True  False,v是user_id或user_name的对象,是否有.属性的primarykey方法
                if not v.primary_key:
                    # print(v.name)   # user_name     pwd
                    # 去除掉主键之后,获取其他的
                    keys.append(v.name)     # 字段的属性
                    # print(v.column_type)    # column_type
                    # print(v.name)    # user_name    print(k)    # user_name   k=v.name
                    # print(keys)     # ['user_name', 'pwd']   将所有的key保存至列表中
                    # 存表中除了主键以外的字段值,若值没有,则使用默认值
                    values.append(
                        getattr(self,v.name,v.default)  # 通过反射获得self中的name对应的值
                    )   # self是他传进来的关键字参数,被打散为字典,也可按key取值
    
                    # print(v)    # <__main__.StringFiled object at 0x000002B56E39E358>
                    # print(v.name,v.default)      # user_name None
                    # print(self)  #  {'user_name': '小明', 'pwd': 123}
                    # print(self.get(v.name))     # 小明
                    # print(getattr(self,v.name))     # 小明
                    # print(values)               # ['小明']
                    # 存放?号的,有几个字段,添加几个?
                    args.append('?')
                    # print(args)     # ['?', '?']
    
            # 编写sql语句指令
            sql = 'insert into %s(%s) values (%s)' % (
                self.table_name,
                ','.join(keys),     # ['?', '?']变为 user_name,pwd
                ','.join(args)
            )
            # 将?替换
            sql = sql.replace('?','%s')
            # 传值到上传函数
            mysql.my_execute(sql,values)
            # print(keys)
    
    ----------------------------------------------------------
    if __name__ == '__main__':
        # 增
        obj = User(user_name='nihao',pwd=123)
        obj.orm_insert()
        res = User.select()
        print(res)
        '''
    [{'user_id': 1, 'user_name': 'tank', 'pwd': '123'}, {'user_id': 2, 'user_name': 'nick', 'pwd': '321'}, {'user_id': 3, 'user_name': 'nihao', 'pwd': '123'}, {'user_id': 4, 'user_name': 'nihao', 'pwd': '123'}]'''
    
    

    修改更新数据

     # 更新方法数据(修改数据)固定用主键当做查询方法
        def orm_update(self):
            print(0,self)       # 0 {'user_id': 3, 'user_name': '哈哈哈', 'pwd': '123'}
            '''sql = update 表名 set k1=v1, k2=v2 where id=主键值'''
            myslq = MySQLClient()
            # 字段名
            keys = []
            # 字段值
            values = []
            # 主键内容 id=主键名
            primary_key = None  # 主键名 = 主键序列号   id = 12
    
            for k,v in self.mappings.items():
                # print(0,k,v)
                # 0 user_id <__main__.IntegerField object at 0x000001A110BCF1D0>
                # 0 user_name <__main__.StringFiled object at 0x000001A110BCF400>
                # 0 pwd <__main__.StringFiled object at 0x000001A110BCF438>
    
                # 判断有无主键的存在,得到有主键的字段,便于where条件查询
                if v.primary_key:
                    primary_key = v.name + '= %s' % getattr(self,v.name)
                    # print(1,primary_key)      # 1 user_id= 3
                else:
                    # 剩下的没有主键的字段都是想要修改的字段与新值
                    keys.append(v.name + '=?')
                    values.append(
                        getattr(self,v.name)  # 将用户传入的参数利用getattr方法进行获得值
                    )
                    # print(2,self)
                    # 2 {'user_id': 3, 'user_name': '哈哈哈', 'pwd': '123'}
                    # 2 {'user_id': 3, 'user_name': '哈哈哈', 'pwd': '123'}
                    # print(3,getattr(self,v.name))
                    #     3 哈哈哈
                    #     3 123
            # 语句# sql: update table set k1=?, k2=? where id=pk; #
            sql = 'update %s set %s where %s' % (
                self.table_name,
                ','.join(keys),
                primary_key
            )
            # print(4,sql)
            # 4 update user_info set user_name=?,pwd=? where user_id= 3
    
            # 替换其中的?号
            sql = sql.replace('?','%s')
            myslq.my_execute(sql,values)
            
            
    ---------------------------------------------------------
    if __name__ == '__main__':
        # 修改更新
        user_obj = User.select(user_name='hhh')[0]
        # print(user_obj) # {'user_id': 3, 'user_name': 'nihao', 'pwd': '123'}
        user_obj.user_name = '哈哈哈'
        # 对象.属性获得字段,将字段赋值为'哈哈哈',调用update函数将自身传入进去,然后根据sql语句更新
        user_obj.orm_update()
    
  • 相关阅读:
    第四周进度条
    单元测试
    第四周开发日志(psp)
    软件工程个人作业03
    第四周课堂作业——单元测试
    进度条 第三周
    开发日志
    软件工程个人作业2
    《构建之法》阅读笔记01
    第一二周进度条
  • 原文地址:https://www.cnblogs.com/fwzzz/p/11818908.html
Copyright © 2011-2022 走看看