大家好,我是你的Odoo技术伙伴。在Odoo开发中,我们每天都在和记录集(Recordsets)打交道,比如 self.env['res.partner'].search([...])
或者 order.order_line
。我们看似在直接操作数据库记录,但实际上,我们与真实数据之间,始终站着一位神通广大的"守卫"------这就是**代理模式(Proxy Pattern)**在Odoo中的体现。
今天,我们将揭开Odoo ORM这层神秘的面纱,深入探讨代理模式是如何作为Odoo数据访问的核心,为我们提供懒加载、权限控制、缓存等一系列强大功能的。
一、什么是代理模式?
让我们先从一个生活中的例子开始:使用银行卡取款。
你不会直接跑到银行的金库里去拿现金。相反,你会使用一张银行卡(代理 Proxy)在ATM机上操作。
- 真实对象(Real Subject): 银行金库里的现金。
- 代理(Proxy): 你的银行卡和ATM机系统。
- 客户端(Client): 你。
这个代理为你做了很多事:
- 访问控制(Access Control): 它会验证你的密码,检查你的账户余额,确保你不会取走不属于你的钱或超出限额。
- 简化交互(Simplification): 你只需简单的插卡、输密码、按金额,复杂的后台账务处理、金库调动等都由它完成。
- 附加功能(Additional Functionality): 它会记录你的交易日志,打印凭条。
转换成软件设计的语言:
代理模式为另一个对象提供一个替身或占位符,以控制对这个对象的访问。代理对象与真实对象实现相同的接口,使得客户端在不知道的情况下,可以透明地使用代理对象。
二、Odoo的ORM记录集:无处不在的智能代理
在Odoo中,最核心的代理模式应用就是ORM的记录集(Recordsets)。当你执行 partners = self.env['res.partner'].browse([1, 2, 3])
时,你得到的 partners
变量并不是一个包含了所有 res.partner
数据的Python列表或字典。
它是一个代理对象。
这个代理对象里面只包含了三样东西:
- 模型的名称:
'res.partner'
- 记录的ID列表:
(1, 2, 3)
- 环境(Environment):
self.env
,包含了上下文、用户、缓存等信息。
它是一个极其轻量级的"占位符"。
懒加载(Lazy Loading):代理模式的核心优势
现在,神奇的事情发生了。当你第一次尝试从这个代理对象中读取某个字段的值时,比如 partners[0].name
,会发生什么?
- 触发代理 : 代理对象被"唤醒"。它发现自己还没有
id=1
的记录的name
字段数据。 - 执行查询 : 它会利用自己的信息(模型名、ID列表、环境),向数据库发起一个SQL查询,例如
SELECT name, ... FROM res_partner WHERE id IN (1, 2, 3)
。注意,为了效率,Odoo通常会一次性**预取(Prefetch)**该记录集所有记录的常用字段数据,而不仅仅是name
。 - 填充缓存: 查询到的数据会被填充到代理对象内部的缓存中,同时也存入当前环境的全局缓存。
- 返回结果 : 将
name
字段的值返回给你。
当你第二次访问 partners[0].name
,或者访问同样被预取了的 partners[0].email
时,代理会直接从自己的缓存中返回数据,不会再查询数据库。
这就是懒加载 :直到你真正需要数据时,才去数据库加载它。这极大地提升了性能,避免了在 search
或 browse
时就加载所有可能用到的字段,造成巨大的内存和I/O开销。
代理模式提供的附加功能
Odoo的记录集代理远不止懒加载这么简单,它像那位银行卡守卫一样,提供了众多关键的附加功能:
1. 访问权限控制(Access Control Proxy)
当你尝试读取或写入数据时,比如 partners.write({'name': 'New Name'})
,代理会做如下检查:
- 它会利用
self.env.user
信息,调用模型的check_access_rights('write')
和check_access_rule('write')
方法。 - 如果当前用户没有写入权限,或者违反了记录规则(Record Rules),操作将被拒绝,并抛出
AccessError
异常。
这个过程对开发者是完全透明的,你无需在每个 write
操作前手动检查权限。代理已经为你筑起了一道安全防线。
2. 缓存管理(Caching Proxy)
如前所述,代理对象在首次读取后会缓存数据。这个缓存是分层的:
- 记录集缓存: 存在于代理对象自身,生命周期与该对象相同。
- 环境缓存 : 存在于
self.env
中,在整个事务(request)期间有效。
这意味着,在同一次请求中,如果你通过不同的记录集代理访问到同一个记录的同一个字段,第二次访问会直接命中环境缓存,实现高效的数据共享。
3. 事务管理与脏数据跟踪
当你对一个记录集进行写操作时,例如 partners.name = 'Updated'
,代理并不会立刻执行 UPDATE
SQL语句。它只是在自己的内部状态中将这条记录标记为"脏数据"(dirty)。
直到事务提交时,Odoo的ORM才会统一收集所有被标记的脏数据,一次性地将变更写入数据库。这同样是由代理在背后默默完成的。
三、代码中的体现:扩展方法即扩展代理行为
当我们通过 _inherit
扩展一个模型并重写其方法(如 write
或 create
)时,我们实际上是在扩展这个模型的代理行为。
示例:在写入联系人时记录审计日志
python
# models/res_partner.py
from odoo import models, api
class ResPartnerAudit(models.Model):
_inherit = 'res.partner'
def write(self, vals):
# 1. 代理执行附加的前置操作
old_names = {p.id: p.name for p in self}
# 2. 调用 super(),将请求传递给原始的代理行为(或链上的下一个代理)
# 原始行为会处理权限检查、实际的数据更新等
res = super(ResPartnerAudit, self).write(vals)
# 3. 代理执行附加的后置操作
if 'name' in vals:
for partner in self:
partner.message_post(body=f"Name changed from '{old_names[partner.id]}' to '{partner.name}'")
return res
在这个例子中,我们的 ResPartnerAudit
类就好像一个更外层的代理,包裹住了Odoo原生的 res.partner
代理。当 write
被调用时:
- 我们的代码先执行(记录旧值)。
- 然后通过
super()
,将控制权交还给内部的代理,让它去完成核心的写入、权限检查和缓存刷新工作。 - 内部代理完成后,控制权再次回到我们的代码,执行后置操作(记录chatter消息)。
整个过程就像层层嵌套的代理,每一层都可以添加自己的逻辑,同时又不破坏核心功能。
结论
代理模式是Odoo ORM的基石。它并非一个需要你手动创建的类,而是你日常使用的 records
对象本身。正是这个强大的代理机制,为我们带来了:
- 高性能的懒加载和预取机制。
- 透明且强制的安全权限控制。
- 高效的缓存管理。
- 事务性的写操作。
理解Odoo记录集本质上是一个代理,会让你对Odoo的性能优化、权限控制和事务处理有更深刻的认识。你会明白,为什么我们应该尽量操作记录集而不是ID列表,为什么 super()
在重写ORM方法中如此重要。
下一次,当你写下 order.partner_id.name
时,请记住,你正在与一个聪明、高效、安全的代理守卫对话,它正在为你打理着与数据库之间的一切复杂事务。