有时候,有些业务场景需要返回一些特定的数据。在调用后端接口时,比较频繁。不断的和数据库进行交互。 此时,我们就可以使用@ormcache
装饰器装饰该接口。将相同条件的数据,存储在缓存中,下次相同的查询条件就可以直接从缓存中拿。
举栗子:
现在,我有一张表params
。用来存放系统的各个模块的参数以及参数值。
1 2 3 4 5 6 7 8 class Params (models.AbstractModel) : _name = 'params' _description = 'Params' module_id = fields.Many2one('ir.module.module' , string='Module' ) org_id = fields.Many2one('ir.orgs' , string='Org' ) key = fields.Char(string="Params Key" ) value = fields.Char(string="Params Value" )
下面我们定义一个获取参数的方法get_params
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def _get_params (self, module_id=False, key_list=False) : """ :desc: 获取参数 :param module_id: 模块ID。 :param key_list: 参数的键 列表。 :return: 返回参数集合 """ domain = [('module_id' , '=' , module_id)] if key_list and isinstance(key_list, list): domain.append(('key' , 'in' , key_list)) records = self.search(domain) return records def get_params (self, module_id=False, key_list=False) : """ :desc: 获取参数 :param module_id: 模块ID。 :param key: 参数的键。 :return: 返回参数集合 """ records = self._get_params(module_id, key_list) return records.read()
我们正常查询参数,可以通过直接调用get_params方法获取。
1 2 3 module_records = self.env['params' ].get_params(module_id=1 ) name_records = self.env['params' ].get_params(module_id=1 , key=['name' , ]) age_records = self.env['params' ].get_params(module_id=1 , key=['age' , ])
此时,我们可以正常的查询出结果。但是如果查询条件变成这样呢?
1 2 3 4 5 6 module_records = self.env['params' ].get_params(module_id=1 ) module_records = self.env['params' ].get_params(module_id=1 ) module_records = self.env['params' ].get_params(module_id=1 )
我们发现,相同条件,一直和数据库进行交互操作,明显的占用资源。此时,我们的ormcache
就可以派上用场了,更改查询方法。
1 2 3 4 5 6 7 8 9 10 @ormcache() def get_params (self, module_id=False, key_list=False) : """ :desc: 获取参数 :param module_id: 模块ID。 :param key: 参数的键。 :return: 返回参数集合 """ records = self._get_params(module_id, key_list) return records.read()
再执行上述的操作, 打断点发现,我们的get_params
方法中代码只执行了一次。这就降低了与数据库交互的频率。
但是我们发现,再执行name_records = self.env['params'].get_params(module_id=1, key=['name', ]) # 查询模块id=1并且key='name'的参数
的时候,还是和上面的结果一样。原因是我们没有指定参数。
我们下面指定参数:
1 2 3 4 5 6 7 8 9 10 @ormcache('module_id') def get_params (self, module_id=False, key_list=False) : """ :desc: 获取参数 :param module_id: 模块ID。 :param key: 参数的键。 :return: 返回参数集合 """ records = self._get_params(module_id, key_list) return records.read()
再次执行
1 2 3 module_records = self.env['params' ].get_params(module_id=1 ) name_records = self.env['params' ].get_params(module_id=1 , key=['name' , ]) age_records = self.env['params' ].get_params(module_id=1 , key=['age' , ])
我们发现,虽然执行了两次,但是 name_records
和age_records
是一样的。 原因是我们在装饰器中指定的参数,作为缓存中存储的条件并没有发生变化
所以,我们再加一个参数。
1 2 3 4 5 6 7 8 9 10 @ormcache('module_id', 'key_list') def get_params (self, module_id=False, key_list=False) : """ :desc: 获取参数 :param module_id: 模块ID。 :param key: 参数的键。 :return: 返回参数集合 """ records = self._get_params(module_id, key_list) return records.read()
运行发现,报错了。原因是作为缓存的键不能是可变对象,和字典的键一样。 因此,我们做出改变。
1 2 3 4 5 6 7 8 9 10 @ormcache('module_id', 'str(key_list)') def get_params (self, module_id=False, key_list=False) : """ :desc: 获取参数 :param module_id: 模块ID。 :param key: 参数的键。 :return: 返回参数集合 """ records = self._get_params(module_id, key_list) return records.read()
我们再执行
1 2 3 module_records = self.env['params' ].get_params(module_id=1 ) name_records = self.env['params' ].get_params(module_id=1 , key=['name' , ]) age_records = self.env['params' ].get_params(module_id=1 , key=['age' , ])
打断点发现,方法又被调用3次。但是接下来的查询,如果有相同条件的就不会再执行了。
但是,问题又来了,我参数表 修改参数、新增参数、删除参数。我们再次使用相同条件查询时候,发现我们获得的数据根本就不是我们想要的。出现了数据脏读。
原因是什么呢?我们在对表进行修改、增加、删除操作时候,没有清除缓存导致的。
所以,我们再次做出更改。
1 2 3 4 5 6 7 8 9 10 11 12 @api.model def create (self, vals) : self.clear_caches() return super(Params, self).create(vals) def write (self, vals) : self.clear_caches() return super(Params, self).write(vals) def unlink (self) : self.clear_caches() return super(Params, self).unlink()
至此,就已经能够正常使用了。
下面,带大家浏览一下源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 class ormcache (object) : """ LRU cache decorator for model methods. The parameters are strings that represent expressions referring to the signature of the decorated method, and are used to compute a cache key:: @ormcache('model_name', 'mode') def _compute_domain(self, model_name, mode="read"): ... For the sake of backward compatibility, the decorator supports the named parameter `skiparg`:: @ormcache(skiparg=1) def _compute_domain(self, model_name, mode="read"): ... Methods implementing this decorator should never return a Recordset, because the underlying cursor will eventually be closed and raise a `psycopg2.OperationalError`. """ def __init__ (self, *args, **kwargs) : self.args = args self.skiparg = kwargs.get('skiparg' ) def __call__ (self, method) : self.method = method self.determine_key() lookup = decorator(self.lookup, method) lookup.clear_cache = self.clear return lookup def determine_key (self) : """ Determine the function that computes a cache key from arguments. """ if self.skiparg is None : args = formatargspec(*getargspec(self.method))[1 :-1 ] if self.args: code = "lambda %s: (%s,)" % (args, ", " .join(self.args)) else : code = "lambda %s: ()" % (args,) self.key = unsafe_eval(code) else : self.key = lambda *args, **kwargs: args[self.skiparg:] def lru (self, model) : counter = STAT[(model.pool.db_name, model._name, self.method)] return model.pool.cache, (model._name, self.method), counter def lookup (self, method, *args, **kwargs) : d, key0, counter = self.lru(args[0 ]) key = key0 + self.key(*args, **kwargs) try : r = d[key] counter.hit += 1 return r except KeyError: counter.miss += 1 value = d[key] = self.method(*args, **kwargs) return value except TypeError: _logger.warn("cache lookup error on %r" , key, exc_info=True ) counter.err += 1 return self.method(*args, **kwargs) def clear (self, model, *args) : """ Clear the registry cache """ model.pool._clear_cache()
通过阅读源码以及相关注释, 我们了解到 @ormcache
的内部是通过 LRU (最近最少使用) 缓存机制实现的。 不知道的同学,自己去百度一下。不过有一个注意事项:
就是对于返回记录集的方法,使用改装饰器是无效的。原因是不可序列化。
以上。