ORM: SQLAlchemy 初識
ORM: Object Relational Mapper.
目前Python有很多ORM工具可以將資料庫映像為Python的Objects物件。
其中比較知名的有Django的ORM,SQLAlchemy, PostgreSQL等。
SQLAlchemy
有更多的人維護,功能也比較齊全。所以一般是我們的首選項。
對於 SQLAlchemy
的使用者來說,只要你一開始連線上資料庫,不管是Sqlite,MySQL還是什麼,後面的處理方式完全一樣。這種便利性也是它受歡迎的原因。
拋棄了傳統的自己編織SQL語句、製作模型、連線資料庫方式, SQLAlchemy
直接把這些東西全包在黑盒裡面,讓我們完全不需要去管。連SQL-Injection注入這種東西也被它幫忙防範了。這樣一來,可以說在連線資料庫方面,幫我們節省了最少一半以上的程式碼。
甚至連資料查詢,SQLAlchemy也代替了SQL語句,而使用了專門的類似MongoDB的 Object.query.filter_by(name='Jason').all()
這種方法。
安裝:
# 安裝sqlalchemy $ pip install sqlalchemy
安裝Drivers:
# Sqlite # 不需要,Python自帶 # MySQL $ pip install pymysql # Postgresql $ pip install psycopg2
SQLAlchemy自身不帶資料庫driver,需要我們自己安裝,並在連線時候指定。
而這些driver,實際上就是我們曾經手動連線資料庫所用的包。而SQLAlchemy只是代替我們使用這些同樣的包。
連線資料庫
建立一個sqlite的ORM引擎:
from sqlalchemy import create_engine # 連線格式為:sqlite://<Hostname>/<path> engine= create_engine('sqlite:///foo.db', echo=True)
建立一個MySQL的ORM引擎:
from sqlalchemy import create_engine # 連線格式為:dialect+driver://username:password@host:port/database engine= create_engine('mysql+pymysql://root:password123@localhost/db_test_01', echo=True)
資料庫的位置(三斜槓為相對路徑,四斜槓為絕對路徑):
# 使用絕對路徑的資料庫檔案(////),如/tmp/mydatabase.db engine= create_engine('sqlite:////tmp/mydatabase.db') # 使用當前「執行位置」資料庫檔案(///或///./) engine= create_engine('sqlite:///mydatabase.db') # 使用當前「執行位置」父級目錄(///../)的資料庫檔案 engine= create_engine('sqlite:///../mydatabase.db') # 使用當前「指令碼位置」的資料庫檔案 import os cwd = os.path.split(os.path.realpath(__file__))[0] engine= create_engine('sqlite:///{}/mydatabase.db'.format(cwd))
Create Tables 建立表
注意:不同於SQL語句,SQLAlchemy中的表名是 完全區分大小寫的 !
建立一個Schema表(指單純表,不包含ORM物件):
from sqlalchemy import create_engine, MetaData from sqlalchemy import Table, Column from sqlalchemy import Integer, String, ForeignKey engine= create_engine('mysql+pymysql://root:password123@localhost/db_test_01', echo=True) metadata = MetaData(engine) # 建立一個表 user_table = Table( 'tb_user', metadata, Column('id', Integer, primary_key=True), Column('name', String(50)), Column('fullname', String(100)) ) # 讓改動生效 metadata.create_all()
建立一個ORM物件(包括表):
# 匯入表格建立引擎 from sqlalchemy import create_engine # 匯入列格式 from sqlalchemy import Column, Integer, String, ForeignKey # 匯入建立ORM模型相關 from sqlalchemy.ext.declarative import declarative_base Base= declarative_base() class User(Base): __tablename__ = 'tb_Person' id = Column('id', Integer, primary_key=True) username = Column('username', String, unique=True) engine = create_engine('sqlite:///test.sqlite', echo=True) Base.metadata.create_all(bind=engine)
用普通表Table和ORM物件建立的表有什麼不同?
他們在資料庫中建立的,是完全相同的表!唯一區別是,Table建立的不包含ORM物件,也就是不提供讓你直接操作Python物件的功能。
這麼做的好處是,有很多隻是關聯作用的表,沒有必要生成ORM物件。
刪除資料庫中的表
# engine = ... # Base = ... # 逐個ORM物件刪除對應的表,如User類 User.__table__.drop(engine) # 刪除全部表 Base.metadata.drop_all(engine)
設計或除錯過程中,我們經常要頻繁改動表格,所以有必要在建立表格前把測試資料庫中的表都清除掉,再建立新的定義。
Insertion 插入資料
將資料新增到資料庫:
# ... # 匯入session相關(用於新增資料) from sqlalchemy.orm import sessionmaker, relationship user = User() user.id = 1 user.username = 'Jason' Session = sessionmaker(bind=engine) session = Session() session.add(user) session.commit() session.close()
注意:這裡的 session
和網站上的session概念有點不一樣。這裡是用來commit提交資料庫變動的工具。
批量新增資料(向add_all()傳入列表):
session.add_all( [user1, user2, user3] )
新增每條資料的時候自動flush():
session = sessionmaker(bind=engine, autoflush=True)
autoflush
是在每次 session.add()
自動執行 session.flush()
,即在插入資料庫之前就在記憶體中生成所有物件的動態資料(如主鍵ID等)。一般預設是選false,因為會影響效率。最好是需要的時候,才手動執行 session.flush()
具體緣由,看下一節“資料生效”。
Take effect 資料生效
SQLAlchemy中的 create_all()
和 session.commit()
都是直接讓python檔案中定義的物件在資料庫中生效的語句。在此之前,無論怎麼定義,資料都是在記憶體中,而沒有在資料庫中的。
注意區分:
create_all session.commit()
這兩個的順序,當然是先建立表格,再插入資料。
只是,如果我們知道了這個原理,在編碼中才能比較運用自由。比如,連 create_engine()
建立引擎,我們都可以在後面定義,而沒必要非得寫在檔案頭,即所有的ORM定義之前。
create_engine
只要定義在所有ORM類和Schema表之後即可。
此後,我們再開始進行資料插入工作,也就利用到了session。
session過程中呢,我們也會遇到互相引用主鍵外來鍵ID的情況。但是注意,這時候因為還沒有使用最終的 session.commit()
真正提交資料到資料庫中,這些ID是沒有值的。
解決辦法就是利用內建的方法 session.flush()
,將session中已新增的所有物件填充好資料,但是這時候還沒有提交到資料庫,只是我們內部可以正常訪問各種ID了。
更新/刪除資料
更新:
# Get a row of data me = session.query(User).filter_by(username='Jason').first() # Method 1: me.age += 1 session.commit() # Method 2: session.query().filter( User.username == 'Jason' ).update( {"age": (User.age +1)} ) session.commit() # Method 3: setattr(user, 'age', user.age+1) session.commit()
Get Primary Key Value 獲取主鍵值
#sqlalchemy can't get primary key
, #sqlalchemy 如何獲得主鍵的值
這個問題花了我很多時間探索查詢,不得其解,才明白原來是很顯然的事。
雖然在沒有用session或engine插入資料之前,我們可以直接瀏覽從ORM建立的物件中的屬性值。
但是這個時候無論如何都獲取不到 primar_key
主鍵列的值。
因為這時候主鍵還沒有插入資料庫,作為 動態的值
,在資料庫沒生效之前也就為None。
為什麼需要獲取 value of primary_key
?考慮如下這些場景:
foreign key
那麼該怎麼獲取主鍵ID呢?
如果要想在插入資料之前就獲取主鍵等 動態列
的值,那麼有這幾種方法:
- 直接利用SQLAlchemy建立類直接的內部關聯,而不直接使用ID
- 主表插入資料,另session生效後,再用query獲取相應物件,來得到它的ID。
- (*) 主表先用
session.add(..)
,再session.flush()
,然後就可以獲取ID,最後再session.commit()
- 不使用primary key主鍵,自己手動建立ID,這樣來隨便獲取。
推薦做法如下:
即每次新建立物件後,立刻session.add(..),然後立刻session.flush(),全部都新增好的文末,再session.commit().
Query 查詢
注意:query是通過session進行的,也就是必須在 session.commit()
之後才能進行查詢,否則會報錯。
這裡將的query查詢,指的都是 在插入到資料庫生效之後
。理解這個很重要,因為在物件未插入到資料庫之前,很多主鍵、外來鍵等內容都是不存在的,也就無法查詢到。
參考:pythonsheets - Object Relational basic query
查詢資料:
session.commit() # ... users = session.query(User).all() # 返回的是多個User類的物件:>>> [ <User 1>, <User 2>, .... ] for u in users: print(u.id, u.username)
常用查詢方法:
# 獲取某ORM中資料 .query(ORM類名) >>> session.query( User ).all()# All rows of data >>> session.query( User ).first()# First row of data as an object # 查詢結果排序 .order_by(類名.列名) >>> session.query(User).order_by( User.birth ).all() # 篩選結果 .filter( True/False 表示式 ) >>> session.query(User).filter( User.name != 'Jason' ).all() >>> session.query(User).filter( User.name.like('%ed%') ).all()# Fuzzy search >>> session.query(User).filter( User.id in [1, 2, 3] ).all()# IN >>> session.query(User).filter( ~ User.id in [4, 5, 6] ).all()# NOT IN >>> session.query(User).filter( User.school == 'MIT', User.age < 24 ).first()# AND >>> session.query(User).filter( _or(User.school == 'MIT', User.age < 24) ).first()# OR