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()
posted @ 2019-11-04 16:55  W文敏W  阅读(111)  评论(0编辑  收藏  举报