07.與實體實例一起工作

07.與實體實例一起工作

創(chuàng)建一個實體實例

在 Pony 中創(chuàng)建一個實體實例,就像在 Python 中創(chuàng)建一個普通對象一樣:

customer1 = Customer(login="John", password="***",
                     name="John", email="john@google.com")

在Pony中創(chuàng)建一個對象時,所有的參數(shù)都應(yīng)該作為關(guān)鍵字參數(shù)指定,如果一個屬性有一個默認(rèn)值,可以省略它。

所有創(chuàng)建的實例都屬于當(dāng)前的db_session(),在一些對象-關(guān)系映射器中,你需要調(diào)用對象的save()方法來保存它。
這很不方便,因為程序員必須跟蹤哪些對象被創(chuàng)建或更新了,而且不能忘記在每個對象上調(diào)用save()方法。

Pony會跟蹤哪些對象被創(chuàng)建或更新,并在當(dāng)前db_session()結(jié)束后自動保存在數(shù)據(jù)庫中。
如果你需要在離開db_session()作用域之前保存新創(chuàng)建的對象,可以通過flush()commit()函數(shù)來完成。

從數(shù)據(jù)庫中加載對象

通過主鍵獲取對象

最簡單的情況是當(dāng)我們想通過主鍵來檢索一個對象。

在Pony中,用戶只需將主鍵放在類名之后的方括號中即可,例如,要提取一個主鍵值為123的客戶,我們可以這樣寫道

customer1 = Customer[123]

同樣的語法也適用于具有復(fù)合主鍵的對象,我們只需要按照實體類描述中定義屬性的順序列出復(fù)合主鍵的元素,用逗號隔開,就可以了。

order_item = OrderItem[order1, product1]

如果不存在這樣的主鍵對象,Pony會引發(fā)ObjectNotFound異常。

通過唯一的屬性組合獲得一個對象

如果你想通過屬性組合來檢索一個對象,你可以使用實體的get()方法。
在大多數(shù)情況下,它用于通過二級唯一性鍵來獲取一個對象,但它也可以用于通過其他任何屬性組合來搜索。
作為get()方法的參數(shù),你需要指定屬性的名稱和值,例如,如果你想接收一個名稱為 "Product 1 "的產(chǎn)品,而你認(rèn)為數(shù)據(jù)庫中只有一個產(chǎn)品在這個名稱下,你可以這么寫:

product1 = Product.get(name='Product1')

如果沒有找到對象,get()返回None,如果找到多個對象,則會拋出MultipleObjectsFoundError異常。

如果對象在數(shù)據(jù)庫中不存在,當(dāng)我們想獲得None而不是ObjectNotFound異常時,你可能想使用帶主鍵的get()方法。

方法get()也可以接收一個lambda函數(shù)作為一個定位參數(shù),這個方法返回的是一個實體的實例,而不是Query類的對象。

獲取多個對象

為了從數(shù)據(jù)庫中檢索多個對象,應(yīng)該使用實體的select()方法,它的參數(shù)是一個lambda函數(shù),它有一個單一的參數(shù),象征著數(shù)據(jù)庫中的一個對象的實例。

在這個函數(shù)中,你可以編寫條件,通過這些條件來選擇對象,例如,如果你想找到所有價格高于100的產(chǎn)品,你可以寫出:

products = Product.select(lambda p: p.price > 100)

這個 lambda 函數(shù)不會在 Python 中執(zhí)行,相反,它將被翻譯成下面的SQL查詢:

SELECT "p"."id", "p"."name", "p"."description",
       "p"."picture", "p"."price", "p"."quantity"
FROM "Product" "p"
WHERE "p"."price" > 100

select()方法會返回一個Query類的實例,如果你開始迭代這個對象,SQL查詢將被發(fā)送到數(shù)據(jù)庫中,你將得到實體實例的序列,例如,你可以這樣打印出所有產(chǎn)品的名稱和價格:

for p in Product.select(lambda p: p.price > 100):
    print(p.name, p.price)

如果你不想迭代查詢,但只需要得到一個對象的列表,你可以這樣做:

product_list = Product.select(lambda p: p.price > 100) [:]

這里我們從查詢中得到一個完整的切片[:],這相當(dāng)于將查詢轉(zhuǎn)換為一個列表:

product_list = list(Product.select(lambda p: p.price > 100))

在查詢中使用參數(shù)

你可以在查詢中使用變量,Pony將把這些變量作為參數(shù)傳遞給SQL查詢。
Pony中的聲明式查詢語法的一個重要優(yōu)勢是,它提供了完全的保護,防止SQL注入,因為所有的外部參數(shù)都會被正確地轉(zhuǎn)義。

下面是一個例子:

x = 100
products = Product.select(lambda p: p.price > x)

這個SQL查詢可能被轉(zhuǎn)換為下面這個樣子:

SELECT "p"."id", "p"."name", "p"."description",
       "p"."picture", "p"."price", "p"."quantity"
FROM "Product" "p"
WHERE "p"."price" > ?

這樣一來,x的值就會作為SQL查詢參數(shù)傳遞,完全消除了SQL注入的風(fēng)險。

排序查詢結(jié)果

如果你需要按照一定的順序?qū)ο筮M行排序,你可以使用Query.order_by()方法。

Product.select(lambda p: p.price > 100).order_by(desc(Product.price))

在這個例子中,我們將所有價格高于100的產(chǎn)品的名稱和價格按降序顯示。

Query對象的方法修改了將被發(fā)送到數(shù)據(jù)庫的SQL查詢,下面是上一個例子中生成的SQL:

SELECT "p"."id", "p"."name", "p"."description",
       "p"."picture", "p"."price", "p"."quantity"
FROM "Product" "p"
WHERE "p"."price" > 100
ORDER BY "p"."price" DESC

Query.order_by()方法也可以接收一個lambda函數(shù)作為參數(shù):

Product.select(lambda p: p.price > 100).order_by(lambda p: desc(p.price))

在lambda函數(shù)中的..code-block::中,Python 方法允許使用高級排序表達(dá)式,例如,這樣就可以按照客戶的訂單總價按降序排序:

Customer.select().order_by(lambda c: desc(sum(c.order.total_price)))

為了按多個屬性對結(jié)果進行排序,你需要用逗號將它們分開,例如,如果你想按價格按降序排序,同時按字母順序顯示價格相似的產(chǎn)品,你可以這樣做:

Product.select(lambda p: p.price > 100).order_by(desc(Product.price), Product.name)

同樣的查詢,但使用lambda函數(shù)將是這樣的。

Product.select(lambda p: p.price > 100).order_by(lambda p: (desc(p.price), p.name))

根據(jù)Python語法,如果從lambda中返回一個以上的元素,需要把它們放到括號中。

限制所選對象的數(shù)量

可以通過使用Query.limit()方法來限制查詢返回的對象數(shù)量,也可以使用更緊湊的Python 切片符號來限制。例如,你可以這樣得到最貴的十個產(chǎn)品:

Product.select().order_by(lambda p: desc(p.price))[:10]

切片的結(jié)果不是查詢對象,而是實體實例的最終列表。

你也可以使用Query.page()方法來方便地對查詢結(jié)果進行分頁:

Product.select().order_by(lambda p: desc(p.price)).page(1)

穿越關(guān)系

在Pony中,你可以遍歷對象關(guān)系:

order = Order[123]
customer = order.customer
print customer.name

Pony試圖盡量減少向數(shù)據(jù)庫發(fā)送的查詢次數(shù)。

為了提高數(shù)據(jù)庫的性能,要盡可能地發(fā)揮每一次數(shù)據(jù)庫查詢的效率,更要盡可能的減少SQL的請求次數(shù)——gthank

在上面的例子中,如果請求的Customer對象已經(jīng)被加載到緩存中,Pony將從緩存中返回該對象而不向數(shù)據(jù)庫發(fā)送查詢。
但是,如果一個對象還沒有加載,Pony仍然不會立即發(fā)送查詢,相反,它將首先創(chuàng)建一個"種子(seed) "對象。
種子是一個只初始化了主鍵的對象,Pony不知道這個對象將被如何使用,而且總是盡可能地只需要主鍵被提供。

在上面的例子中,當(dāng)訪問name屬性時,Pony從數(shù)據(jù)庫中獲取了第三行的對象。通過使用 "種子 "的概念,Pony實現(xiàn)了高效查詢,解決了很多其他映射器的痛點: "N+1 "的問題。

"to-many "的方向上也可以進行遍歷。
例如,如果你有一個Customer對象,你要循環(huán)遍歷它的訂單屬性,你可以這樣做:

c = Customer[123]
for order in c.orders:
    print order.state, order.price

更新一個對象

當(dāng)你給對象屬性賦予新值時,你不需要手動保存每個更新的對象,當(dāng)離開db_session()作用域時,更改將自動保存在數(shù)據(jù)庫中。

例如,為了將主鍵為123的產(chǎn)品數(shù)量增加10個,可以使用下面的代碼:

Product[123].quantity += 10

為了改變同一個對象的多個屬性,可以分別進行:

order = Order[123]
order.state = "Shipped"
order.date_shipped = datetime.now()

或在單行中,使用實體實例的set()方法。

order = Order[123]
order.set(state="Shipped", date_shipped=datetime.now())

當(dāng)你需要從字典中一次性更新多個對象屬性時,set()方法很方便。

order.set(**dict_with_new_values)

如果需要在當(dāng)前數(shù)據(jù)庫會話結(jié)束前保存數(shù)據(jù)庫的更新,可以使用flush()commit()函數(shù)。

Pony總是在執(zhí)行以下方法之前:select()、get()、resores()、execute()和commit(),自動保存在db_session()緩存中積累的修改。

在未來,Pony將支持批量更新,它將允許更新磁盤上的多個對象,而不需要將其加載到緩存中。

update(p.set(price=price * 1.1) for p in Product
                                if p.category.name == "T-Shirt")

刪除一個對象

當(dāng)你調(diào)用一個實體實例的delete()方法時,Pony會將該對象標(biāo)記為刪除,在下面的提交中,該對象將被從數(shù)據(jù)庫中刪除。

例如,我們可以這樣刪除一個主鍵等于123的訂單:

Order[123].delete()

批量刪除

Pony支持使用delete()函數(shù)對對象進行批量刪除。這樣你可以刪除多個對象,而不需要將它們加載到緩存中。

delete(p for p in Product if p.category.name == 'SD Card')
#or
Product.select(lambda p: p.category.name == 'SD Card').delete(bulk=True)

在進行批量刪除時要小心:

  • before_delete()after_delete()鉤子不會在被刪除的對象上被調(diào)用。
  • 如果一個對象被加載到內(nèi)存中,在批量刪除時不會從db_session()緩存中刪除。

級聯(lián)刪除

當(dāng)Pony刪除一個實體的實例時,它還需要刪除它與其他對象之間的關(guān)系。

兩個對象之間的關(guān)系是由兩個關(guān)系屬性定義的,如果關(guān)系的另一邊聲明為Set,那么我們只需要將該對象從該集合中刪除即可。

如果另一個邊被聲明為Optional,那么我們需要將其設(shè)置為None。

如果另一個邊被聲明為 Required,那么我們不能直接將 None指定為該關(guān)系屬性。

在這種情況下,Pony會嘗試對相關(guān)對象進行級聯(lián)刪除。

這個默認(rèn)行為可以使用屬性的cascade_delete選項來改變,默認(rèn)情況下,當(dāng)關(guān)系的另一邊被聲明為 Required時,這個選項被設(shè)置為 True,而對于所有其他的關(guān)系類型,這個選項被設(shè)置為 False

如果關(guān)系的另一端被定義為 Required,并且cascade_delete=False,那么 Pony 會在嘗試刪除時引發(fā) ConstraintError 異常。

讓我們考慮幾個例子。

下面的例子在嘗試刪除一個有相關(guān)學(xué)生的組時,會引發(fā)ConstraintError異常:

class Group(db.Entity):
    major = Required(str)
    items = Set("Student", cascade_delete=False)

class Student(db.Entity):
    name = Required(str)
    group = Required(Group)

在下面的例子中,如果一個Person對象有一個相關(guān)的Passport對象,那么如果你將嘗試刪除Person對象,由于級聯(lián)刪除,Passport對象也會被刪除。

class Person(db.Entity):
    name = Required(str)
    passport = Optional("Passport", cascade_delete=True)

class Passport(db.Entity):
    number = Required(str)
    person = Required("Person")

保存數(shù)據(jù)庫中的對象

通常情況下,你不需要手動保存數(shù)據(jù)庫中的實體實例,Pony會在離開db_session()上下文時自動將所有的更改提交到數(shù)據(jù)庫中,這是非常方便的。
同時,在某些情況下,在離開當(dāng)前數(shù)據(jù)庫會話之前,你可能需要在數(shù)據(jù)庫中flush()commit()數(shù)據(jù)。

如果你需要獲取新創(chuàng)建的對象的主鍵,可以在db_session()中手動進行commit(),以獲取這個值:

class Customer(db.Entity):
    id = PrimaryKey(int, auto=True)
    email = Required(str)

@db_session
def handler(email):
    c = Customer(email=email)
    # c.id is equal to None
    # because it is not assigned by the database yet
    commit()
    # the new object is persisted in the database
    # c.id has the value now
    print(c.id)

保存對象的順序

通常情況下,Pony會按照創(chuàng)建或修改對象的順序保存數(shù)據(jù)庫中的對象,在某些情況下,如果需要保存對象的話,Pony可以對SQL INSERT語句進行重新排序。讓我們考慮一下下面的例子:

from pony.orm import *

db = Database()

class TeamMember(db.Entity):
    name = Required(str)
    team = Optional('Team')

class Team(db.Entity):
    name = Required(str)
    team_members = Set(TeamMember)

db.bind('sqlite', ':memory:')
db.generate_mapping(create_tables=True)
set_sql_debug(True)

with db_session:
    john = TeamMember(name='John')
    mary = TeamMember(name='Mary')
    team = Team(name='Tenacity', team_members=[john, mary])

在上面的例子中,我們先創(chuàng)建兩個團隊成員,然后創(chuàng)建一個團隊對象,將團隊成員分配給團隊,TeamMemberTeam對象之間的關(guān)系用TeamMember表中的一列來表示:

CREATE TABLE "Team" (
  "id" INTEGER PRIMARY KEY AUTOINCREMENT,
  "name" TEXT NOT NULL
)

CREATE TABLE "TeamMember" (
  "id" INTEGER PRIMARY KEY AUTOINCREMENT,
  "name" TEXT NOT NULL,
  "team" INTEGER REFERENCES "Team" ("id")
)

Pony在創(chuàng)建john、mary和team對象時,理解為應(yīng)該重新排序SQL INSERT語句,并先在數(shù)據(jù)庫中創(chuàng)建一個Team對象的實例,因為這樣可以使用teamid來保存TeamMember記錄:

INSERT INTO "Team" ("name") VALUES (?)
[u'Tenacity']

INSERT INTO "TeamMember" ("name", "team") VALUES (?, ?)
[u'John', 1]

INSERT INTO "TeamMember" ("name", "team") VALUES (?, ?)
[u'Mary', 1]

保存對象期間的循環(huán)鏈

現(xiàn)在我們假設(shè)想為一個團隊分配一個隊長,為了這個目的,我們需要在我們的實體中添加幾個屬性:Team.captain和反向?qū)傩?code>TeamMember.captain_of:

class TeamMember(db.Entity):
    name = Required(str)
    team = Optional('Team')
    captain_of = Optional('Team')

class Team(db.Entity):
    name = Required(str)
    team_members = Set(TeamMember)
    captain = Optional(TeamMember, reverse='captain_of')

這里是創(chuàng)建實體實例的代碼,并為團隊分配了一個隊長:

with db_session:
    john = TeamMember(name='John')
    mary = TeamMember(name='Mary')
    team = Team(name='Tenacity', team_members=[john, mary], captain=mary)

當(dāng)Pony嘗試執(zhí)行上面的代碼時,會產(chǎn)生以下異常:

pony.orm.core.commitException: 無法保存循環(huán)鏈。TeamMember -> Team -> TeamMember

為什么會出現(xiàn)這種情況呢?

我們來看一下,Pony看到,在數(shù)據(jù)庫中保存johnmary對象時,它需要知道團隊的id,并嘗試著重新排序插入語句。

但對于保存有隊長屬性分配的團隊對象,它需要知道m(xù)ary對象的id,在這種情況下,Pony無法解決這個循環(huán)鏈,并提出一個異常。

為了保存這樣一個循環(huán)鏈,你必須通過添加flush()命令來幫助Pony:

with db_session.john = TeamMember(name)
    john = TeamMember(name='John')
    mary = TeamMember(name='Mary')
    flush() # 將此刻創(chuàng)建的對象保存在數(shù)據(jù)庫中
    team = Team(name='Tenacity', team_members=[john, mary], captain=mary)

在這種情況下,Pony會先將johnmary對象保存在數(shù)據(jù)庫中,然后發(fā)出SQL UPDATE語句來建立與團隊對象的關(guān)系:

INSERT INTO "TeamMember" ("name") VALUES (?)
[u'John']

INSERT INTO "TeamMember" ("name") VALUES (?)
[u'Mary']

INSERT INTO "Team" ("name", "captain") VALUES (?, ?)
[u'Tenacity', 2]

UPDATE "TeamMember"
SET "team" = ?
WHERE "id" = ?
[1, 2]

UPDATE "TeamMember"
SET "team" = ?
WHERE "id" = ?
[1, 1]

實體方法

詳情請參見API參考中的Entity方法部分。

實體鉤子

詳情請參見API參考中的Entity hooks部分。

使用pickle對實體實例進行序列化

Pony允許序列化實體實例、查詢結(jié)果和集合。如果你想將實體實例緩存在外部緩存中(如memcache),你可能需要使用它。

當(dāng)Pony 序列化(pickles)實體實例時,它保存了除集合以外的所有屬性,以避免挑出大量的數(shù)據(jù)集。

如果你需要對集合屬性進行pickle,必須單獨pickle,如:

>>> from pony.orm.examples.estore import *
>>> products = select(p for p in Product if p.price > 100)[:]
>>> products
[Product[1], Product[2], Product[6]]
>>> import cPickle
>>> pickled_data = cPickle.dumps(products)

現(xiàn)在我們可以把序列化好的數(shù)據(jù)放到緩存中,以后,當(dāng)我們再次需要我們的實例時,我們可以把它解開:

>>> products = cPickle.loads(pickled_data)
>>> products
[Product[1], Product[2], Product[6]]

你可以使用pickling將對象存儲在外部緩存中,以提高應(yīng)用程序的性能。當(dāng)你反序列化對象時,Pony會把它們添加到當(dāng)前的db_session()中,就像剛剛從數(shù)據(jù)庫中加載一樣,Pony不會檢查對象是否在數(shù)據(jù)庫中保持相同的狀態(tài)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容