優酷項目之 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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...