• orm框架分析——仿优酷项目


    一、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 |
    +---------+-----------+------+
  • 相关阅读:
    HashMap按键排序和按值排序
    LeetCode 91. Decode Ways
    LeetCode 459. Repeated Substring Pattern
    JVM
    LeetCode 385. Mini Parse
    LeetCode 319. Bulb Switcher
    LeetCode 343. Integer Break
    LeetCode 397. Integer Replacement
    LeetCode 3. Longest Substring Without Repeating Characters
    linux-网络数据包抓取-tcpdump
  • 原文地址:https://www.cnblogs.com/lulingjie/p/11656093.html
Copyright © 2020-2023  润新知