優酷項目之 ORM(資料庫對象關係映射)代碼重寫

来源:https://www.cnblogs.com/suguangti/archive/2019/05/22/10903464.html
-Advertisement-
Play Games

前言: 我們在操作資料庫時候一般都是通過sql代碼來操作mysql資料庫中相關數據,這就需要懂得sql語句,那麼怎麼樣才能在不懂sql語句的情況下通過我們所學的python代碼來實現對mysql資料庫的操作? 當然有這種神奇的操作,其中之一就是今天深入瞭解的ORM對象關係映射(Object Rela ...


前言:

  我們在操作資料庫時候一般都是通過sql代碼來操作mysql資料庫中相關數據,這就需要懂得sql語句,那麼怎麼樣才能在不懂sql語句的情況下通過我們所學的python代碼來實現對mysql資料庫的操作?

  當然有這種神奇的操作,其中之一就是今天深入瞭解的ORM對象關係映射(Object Relational Mapping),本文主要通過python代碼來自己實現mysql資料庫的對象關係映射,達到對前面所學知識的鞏固與加深。

一、先來說說具體映射關係:(記住這個關係,在後面具體代碼實現的時候會用到)

ORM:對象關係映射:
類     =======>     資料庫的一張表
對象      =======>     表的一條記錄
對象點屬性    =======>    記錄某一個欄位對應的值
上面關係分析:
通過python中創建類來實現對資料庫一張表結構的關係產生一種一一對應關係
通過python中對創建的類實例化的對象操作對資料庫一張表進行表記錄的操作的一一對應關係
通過python中對象點屬性的方式來操作記錄表中某一欄位的對應值,的一一對應操作關係

首先來通過代碼層面來映射資料庫表欄位的類型:

# 定義一個類,在映射資料庫中的表結構:
class Field(object): 
    #  先定義一個表結構欄位類,比如 欄位名name、欄位類型column_type、欄位是否為主鍵primary_key、欄位預設值default
    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
    
#  當然欄位的類型很多,可以單獨設特殊的欄位類:比如varchar、int欄位類型,讓它繼承FIeld類就行
class StringField(Field):  #  定義欄位類型varchar
    #  將欄位類型指定為:varchar(255),主鍵預設為False,預設值為None
    def __init__(self, name, column_type='varchar(255)', primary_key=False, default=None):
        #  讓它重寫__init__的基礎上其他地方繼承它的基類Field裡面的__init__方法
        super().__init__(name, column_type, primary_key, default)
        
class IntegerField(Field):  # 定義欄位類型int
    def __init__(self, name, column_type='int', primary_key=False, default=None):
        super().__init__(name, column_type, primary_key, default)

暫時先創建2種常見類型的欄位類型類

接著來看看如何映射資料庫表的結構:

# 創建一個字典對象的過程:t1 = dict(name='sgt', age=18, sex = 'male')
# 讓Models類繼承字典這個類,這樣Models類就繼承了dict類的方法(把一堆關鍵字參數傳進去,返回一個字典)的實例化過程
class Models(dict):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    
    # 除了繼承dict類的方法,我們還需要擁有更多方法,比如當傳入的參數通過對象點(傳入參數關鍵字名)的方法得到參數的
    # 關鍵字值,通過點參數名=參數值來新增傳入的關鍵字參數
    # 繼續分析:傳入參數是關鍵字形式(name='sgt',age = 18...),但是參數不是類中屬性,如果想要通過實例化出的對象點
    # 這個參數key的方式得到value值的目的,可以使用__getattr__來實現,也就是說,實例化出的對象在點這個key
    # 時候,觸發了__getattr__方法,方法返回self.get(key),這裡的self就是繼承dict類通過傳入關鍵字參數返回的字典
    # 類型的的對象,通過點get()就能獲得對應的value值。
    def __getattr__(self, item):  # 在對象獲取它沒有的屬性和方法的時候觸發
        return self.get(item)  # item就是傳入參數的k
    
    # 既然可以點k的方式得到value,那麼還可以點新key=值的方法來增加傳入的關鍵字參數
    def __setattr__(self, key, value):  # 在對象點屬性=值的時候自動觸發
        self[key] = value
        
    # 通過上面的__getattr__和__setattr__的方法實現了實例化出對象的方式讓傳入的參數返回給對象一個字典的
    # 同時又可以讓這個對象點關鍵字中的key得到value值,點key=value值來新增或者設置值的目的
    # 這裡插一嘴:為何要實現這個目的?因為我們通過pymysql模塊實現操作資料庫返回來的數據類型基本都是字典類型外面
    # 套列表的形式,那麼如果想辦法將查詢的結果也變成一個字典對象,那麼查詢裡面的key(欄位名)和value(欄位記錄值)
    # 就特別方便了,同時在新增和插入數據時候會用到這個方法,達到更簡單明瞭的目的。

  上面只是實現了我麽在操作表記錄方面的某些功能,但是我麽知道還沒有達到映射資料庫表結構的目的

  怎麼做呢?想想我們的目的:在映射表結構的時候這個表結構應該有哪些東西?

  回答:表的欄位名們,表的主鍵是哪個欄位,當然還有表名,好像有了這3個關鍵性的因素映射資料庫表結構就差不多達到目的了。

  那麼如何才能實現我們在創建一個映射表結構的一個類的同時這些我們想要的因素都能自動產生呢?

說到自動,又說道創建類的時候,我想我們可以往元類上面想了,前面學習元類的時候我們就可以攔截類的創建過程,在這個過程中加入或者修改,達到我們想要的目的。

  所以說攔截類的創建過程是關鍵,類創建過程會觸發啥?答案是:元類的__new__方法

既然要攔截,肯定是不讓元類的__new__生效,讓我們自己定義一個__new__或者說在元類的__new__觸發之前自己通過自定義__new__來加入一些我們需要的然後再走元類的__new__,此時就能達到目的了。

# 對指定Models的元類為MyMeta
class MyMeta(type):  # 自定義元類必須繼承type才能算是元類,否則就是普通的類
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)

class Models(dict, metaclass=MyMeta):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def __getattr__(self, item):
        return self.get(item)

    def __setattr__(self, key, value):
        self[key] = value
    Myname = 'sgt'
# 這裡創建了類Models的時候,就觸發了我們自定義元類中的__new__方法,所以右鍵就會執行列印,結果依次是
# <class '__main__.MyMeta'>
# ('Models', (<class 'dict'>,), {'__module__': '__main__', '__qualname__': 'Models',
# '__init__': <function Models.__init__ at 0x0000025A17B19BF8>, '__getattr__':
# <function Models.__getattr__ at 0x0000025A17B19C80>, '__setattr__': <function Models.__setattr__ at
# 0x0000025A17B19D08>, 'Myname': 'sgt', '__classcell__': <cell at 0x0000025A17A87618: empty>})
# {}
# 第一行列印的是Models的類
# 仔細看第二行:第一個是Models--類名,第二個是dict這個類--也就是Models的基類,第三個是個字典,看看字典里的
# 內容,一眼瞅過去好像是一個類裡面的內置屬性和自定義屬性(因為看到了Myname這個變數)
# 最後一行就{},關鍵字參數沒傳啥。
# 最後分析一下:創建類的時候我們攔截了類的創建過程,自定義了元類,在類創建的時候讓它走了我們自定義元類裡面的
# __new__方法,這樣,Models這個類一'class'開始申明就開始準備走__new__方法,接著我們看了列印的各個參數:
# 分別是cls-創建的類自己、類名、類的基類們、類屬性字典,所以既然類在創建時候會在__new__傳入這些參數,那麼我們
# 將這些參數進一步明瞭化一下:
class MyMeta(type):  # 自定義元類必須繼承type才能算是元類,否則就是普通的類
    def __new__(cls, class_name, class_bases, class_attrs):
        print(cls)
        print(class_name)
        print(class_bases)
        print(class_attrs)

class Models(dict, metaclass=MyMeta):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def __getattr__(self, item):
        return self.get(item)

    def __setattr__(self, key, value):
        self[key] = value
    Myname = 'sgt'
# 右鍵再次運行一下,發現列印的結果一模一樣,至此我們進一步明確化了__new__的實質了,接下來開始實現我們的初衷
# 在類創建的時候為這個類添加預設的屬性:映射表名、映射表的主鍵欄位名、映射表的自定義屬性(欄位名、對應欄位值)

 攔截類的創建,加入預設表結構屬性

開始攔截類的創建(表結構映射的創建)
class
MyMeta(type): def __new__(cls, class_name, class_bases, class_attrs): # 我們要知道一件事:我們只需要設置我們自己定義(創建類時候你寫的屬性)屬性,其他建類時候預設的一些內置屬性 # 我們是不需要的,或者說我們可以將自己定義屬性集中在一個字典中,這個字典我們起個名字:mappings # # __new__攔截了哪些類的創建:Models、Models的子類,很顯然Models類我們無需攔截,因為我們創建表結構映射的類 # 並不是Models,而應該是繼承了Models的一個類,所以需要排除Models if class_name == 'Models': return type.__new__(cls, class_name, class_bases, class_attrs) # 開始部署自定義的類屬性: # 表名:我們在創建類體代碼的時候會設置個屬性叫table_name=***,如果沒有設置,預設為類名 table_name = class_attrs.get('table_name', class_name) primary_key = None # 後面要找出主鍵是哪個欄位名,這裡先設置個空 mappings = {} # 這個mappings就是我們需要找出的自定義欄位名和對應相關參數 # class_attr={'table_name':'user','id':IntegerField(name='id', primary_key=True),'name':StringField(name='name')...) for k, v in class_attrs.items(): if isinstance(v, Field): # 用欄位類型是否屬於它的基類Field來過濾得到我們想要的自定義的屬性 mappings[k] = v if v.primary_key: # 如果該欄位類型中primary_key=True # 在for迴圈中,因為最初primary_key是None,當第一次找到primary_key時候,將primary_key賦值給該欄位名,當下次在for迴圈 # 中找到primary_key同時primary_key不為空時候就代表又找到了第二個primary_key,此時必須的拋異常,因為一張表不能有2個primary_key if primary_key: raise TypeError('一張表只能有一個主鍵') primary_key = v.name for k in mappings.keys(): # 代碼健壯性行為 class_attrs.pop(k) # 前面將表中自定義欄位集中在mappings里了,此時外面的class_attrs中的內置預設屬性中的自定義欄位 # 為了避免重覆需要刪除。 if not primary_key: # 如果遍歷完了class_attrs還沒有找到primary_key,也需要拋異常,一張表必須要有一個主鍵 raise TypeError('一張表必須有一個主鍵') # 最後將我們自定義的屬性(表名、欄位名和欄位類型的類對象、主鍵欄位名)加入class_attrs(創建這個類的初始屬性中) class_attrs['table_name'] = table_name class_attrs['primary_key'] = primary_key class_attrs['mappings'] = mappings # 加進去之後,我們僅僅是攔截__new__來達到這個目的,關於創建類的其他全部過程還是該怎麼走怎麼在,交個元類去做 return type.__new__(cls, class_name, class_bases, class_attrs) class Models(dict, metaclass=MyMeta): def __init__(self, **kwargs): super().__init__(**kwargs) # 要搞清楚這裡的self是啥?我們肯定知道是個對象,這個對象有點特別,他是個字典對象,因為Models繼承了dict的方法 def __getattr__(self, item): return self.get(item) def __setattr__(self, key, value): self[key] = value
  接下來開始實現對錶的查、改、增:新建一個python文件:mysql_singleton
import pymysql

class Mysql(object):
    def __init__(self):
        self.conn = pymysql.connect(  # 建立資料庫連接
            host='127.0.0.1',
            port=3306,
            user='root',
            password='123',
            database='youku_01',
            charset='utf8',
            autocommit=True
        )
        self.cursor = self.conn.cursor(pymysql.cursors.DictCursor) # 建立游標連接
        
    def close_db(self):  # 健壯性補充
        self.cursor.close()
        self.conn.close()
    
    def select(self, sql, args=None):  #
        self.cursor.execute(sql, args)
        res = self.cursor.fetchall()
        return res  # 返回結果為:[{},{},{}...]
    
    def execute(self, sql, args):  
        # insert、update操作方法,查最多是查不到結果為空,但是改和增的話如果
        # 出問題的話有可能達不到我們想要的結果,所以需要捕獲異常,讓我們能知道修改成功與否
        try:
            self.cursor.execute(sql, args)
        except BaseException as e:
            print(e)

    _instance = None
    @classmethod  # 實現單例,減小記憶體空間的占用
    def singleton(cls):
        if not cls._instance:
            cls._instance = cls()
        return cls._instance
  然後封裝一個個方法:
查:
from mysql_singleton import Mysql  # 導入剛纔新建的文件中的類

    @classmethod
    def select(cls, **kwargs):  # cls:創建的表結構關係映射的類
        ms = Mysql.singleton()  # 創建一個Mysql單例對象ms,通過ms來點類中方法實施對應操作
        if not kwargs:  # 如果不傳關鍵字參數,說明查詢是select * from 表名
            sql = 'select * from %s' % cls.table_name
            res = ms.select(sql)
        # 如果傳入關鍵字參數:select * from 表名 where 欄位名k=傳入欄位v
        else:
            k = list(kwargs.keys())[0]  # 拿出關鍵字中的k
            v = kwargs.get(k)
            sql = 'select * from %s where %s = ?' % (cls.table_name, k)
            # 此處最後一個?就是要傳入的v,這裡用?占位主要為了避免sql註入問題
            sql = sql.replace('?', '%s') # 將?變成%s,解決sql註入問題不傳,後面cursor.execute會自動識別%s傳參
            res = ms.select(sql, v)
        if res:
            return [cls(**i) for i in res]
        # 這裡這樣做的目的:res是查詢到的結果[{},{},..],將其遍歷,然後打散成x=1,y=2的形式當做參數傳入
        # 類參與實例化的過程得到一個對象(因為該類繼承dict,所以得到的是個字典對象),這樣就與我們一開始為何要將類
        # 繼承dict為何要在Models中寫__getattr__和__setattr__方法相關聯。

改:







您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JVM記憶體分為哪幾部分?各個部分的作用是什麼? JVM記憶體分為哪幾部分?各個部分的作用是什麼? JVM記憶體分為哪幾部分?各個部分的作用是什麼? JVM記憶體分為哪幾部分?各個部分的作用是什麼? 1. Java虛擬機記憶體的五大區域 Java的運行離不開Java虛擬機的支持,今天我就跟大家探討一下Java ...
  • 厲害了,2019/05/21 Apache軟體基金會發表博文,宣佈 Dubbo 在 2019/05/20 這天正式畢業,成為 Apache 的頂級項目。 參考:https://blogs.apache.org/foundation/entry/the apache software foundati ...
  • 14.1 基本介紹 -Scala飾運行在Java虛擬機(Java Virtual Machine)之上,因此具有如下特點 1) 輕鬆實現和豐富的Java類庫互聯互通 2) 它既支持面向對象的編程方式,又支持函數式編程 3) 它寫出的程式像動態語言一樣簡潔,但事實上它確是嚴格意義上的靜態語言 14.2 ...
  • Collection集合 數組的長度是固定的,集合的長度是可變的 數組中存儲的是同一類型的元素,可以存儲基本數據類型值。集合存儲的都是對象。而且對象的類型可以不一致。 集合框架 java import java.util. ; public class IteratorDemo{ public st ...
  • 111
    1. 內容大綱 1. 自定義模塊 2. 模塊是什麼? 3. 為什麼要有模塊? 什麼是腳本? 4. 模塊的分類 5. import的使用 第一次導入模塊執行三件事 被導入模塊有獨立的名稱空間 為模塊起別名 導入多個模塊 6. from ... import ... from ... import .. ...
  • 一、TCP簡介 1、TCP介紹 TCP協議,傳輸控制協議(英語:Transmission Control Protocol,縮寫為 TCP)是一種面向連接的、可靠的、基於位元組流的傳輸層通信協議。 TCP通信需要經過創建連接、數據傳送、終止連接三個步驟。 TCP通信模型中,在通信開始之前,一定要先建立 ...
  • 下載安裝 官網https://golang.google.cn/dl/下載,安裝 環境變數配置 參考https://www.jianshu.com/p/5c1873eaf3ca Bash 編輯~/.bash_profile文件(vim ~/.bash_profile,進入vim的正常模式,在正常模式 ...
  • 第一個爬蟲程式——豆瓣新書信息爬取。主要用到 soup 的 find 和 find_all 方法。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...