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()
    
  • 相关阅读:
    vue开发chrome扩展,数据通过storage对象获取
    Vue手动集成less预编译器
    Google Translate寻找之旅
    Javascript Range对象的学习
    Javascript Promises学习
    SublimeText 建立构建Node js系统
    We're sorry but demo3 doesn't work properly without JavaScript enabled. Please enable it to continue.
    npm安装包出现UNMET DEPENDENCY报错
    (转载)命令行说明中格式 尖括号 中括号的含义
    Linux重启网卡服务Failed to start LSB: Bring up/down networking.
  • 原文地址:https://www.cnblogs.com/fwzzz/p/11818908.html
Copyright © 2011-2022 走看看