我的第一个python web开发框架(31)——定制ORM(

  大家只要掌握了ORM简单的组合sql方法,就可以自由发挥,根据自己的需要去创建不同的方法了,也可以随意更换mysql、mssql等数据库。

  本文对应的源码下载

  根据这些要求,我们就需要再增加两个缓存操作方法,用来存储这些特殊的缓存名称,然后在进行修改和删除操作时,同步清除这些特殊缓存。

 1     def get_model_for_cache_of_where(self, where):
 2         """
 3         通过条件获取记录实体————条件必须是额外的主键,也就是说记录是唯一的(我们经常需要使用key、编码或指定条件来获取记录,这时可以通过当前方法来获取)
 4         :param where: 查询条件
 5         :return: 记录实体
 6         """
 7         # 生成实体缓存key
 8         model_cache_key = self.__table_name + encrypt_helper.md5(where)
 9         # 通过条件从缓存中获取记录id
10         pk = cache_helper.get(model_cache_key)
11         # 如果主键id存在,则直接从缓存中读取记录
12         if pk:
13             return self.get_model_for_cache(pk)
14 
15         # 否则从数据库中获取
16         result = self.get_model(where)
17         if result:
18             # 存储条件对应的主键id值到缓存中
19             cache_helper.set(model_cache_key, result.get(self.__pk_name))
20             # 存储记录实体到缓存中
21             self.set_model_for_cache(result.get(self.__pk_name), result)
22             return result

  首先我们需要在初始化方法中,添加一个绑定该数据表的全局缓存变量self.__cache_list,它由表名称+_cache_list组成。

  2.该功能是否是常用的功能,能否封装成公共方法,如果可以就将它封装到逻辑层基类(ORM模块)中去,让所有继承的子类都拥有这个功能

  接着是获取缓存对象方法

  有了这个方法,我们就可以继续对产品分类删除接口进行改造了

 

  如果是数值类型,直接写值就可以了,当然直接赋字符串值也没有关系,因为生成sql是不会自动添加单撇号的

图片 1图片 2

  如果字段是数值型,要让它进行计算,直接像上面这样写也行,可以是多个字段用加号连起来。当然你也可以将字段时读出来进行计算后再赋值提交也没有问题

 1     def __init__(self, db, is_output_sql, table_name, column_name_list='*', pk_name='id'):
 2         """类初始化"""
 3         # 数据库参数
 4         self.__db = db
 5         # 是否输出执行的Sql语句到日志中
 6         self.__is_output_sql = is_output_sql
 7         # 表名称
 8         self.__table_name = str(table_name).lower()
 9         # 查询的列字段名称,*表示查询全部字段,多于1个字段时用逗号进行分隔,除了字段名外,也可以是表达式
10         self.__column_name_list = str(column_name_list).lower()
11         # 主健名称
12         self.__pk_name = str(pk_name).lower()
13         # 缓存列表
14         self.__cache_list = self.__table_name + '_cache_list'

  正常情况下,我们直接通过get_model()方法就可以读取对应的记录了,如果我们想减少数据库的查询,直接在缓存中如何使用呢?直接存取记录实体,由于这些额外的主键并没有与ORM中的编辑与删除操作关联,即在进行编辑与删除操作时不会同步更新用其他主键存储的实体内容,这样就会产生脏数据。所以我们可以换一种思路来实现,我们可以将这些额外的主键和对应的值生成缓存组合key,里面存储对应的记录实体id,也就是说在存储记录实体时,还是使用原来的主键id存储该实体,然后用额外主键和对应值生成缓存组合key中存储主键id,在获取记录实体时,先用这个组合key提取对应的id,再用这个id来获取记录实体。这个说明好像有点绕,大家自己debug一下就很容易明白其中的原理了,下面看代码:

  删除缓存方法也很简单,生成缓存key后,直接调用delete进行删除。对于删除方法,有时候调用不知是不是nosql自身bug问题,还是在主从关系的nosql中读写分离会引起删除失败,如果出现这种情况,可以将delete改为set,将该缓存set为空就可以解决这个问题

    def exists(self, wheres):
        """检查指定条件的记录是否存在"""
        return self.get_count(wheres) > 0

 

  这段代码后半部分可以参考产品的删除接口实现,前半部分需要调用产品方法进行判断处理。

  然后我们还需要一个nosql链接工具包(cache_helper.py),用来对nosql进行set、get、delete和clear操作(存储、获取、删除和清空缓存)

  我们可以更改为:

  接着我们再添加一个清除所有特殊缓存的方法

 1 @delete('/api/product/<id:int>/')
 2 def callback(id):
 3     """
 4     删除指定记录
 5     """
 6     # 编辑记录
 7     sql = """delete from product where id=%s returning id"""
 8     vars = (id,)
 9     # 写入数据库
10     result = db_helper.write(sql, vars)
11     # 判断是否提交成功
12     if result:
13         return web_helper.return_msg(0, '成功')
14     else:
15         return web_helper.return_msg(-1, "删除失败")

 

  还有登录接口最底部,更新管理员最后登录时、登录ip和累加登录次数需要改造,具体代码如下:

  对于缓存基本上就这两个要进行改造的操作了。在实现开发中,我们认真想一想,其实我们还会存在一些特殊的情况,比如说我们对数据进行加工处理后,将加工后的值存储到缓存中,而对相关记录进行修改或删除操作以后,由于这些缓存它与记录并没有关联,所以执行相关操作以后,它就变成孤岛,不会实时同步,产生脏数据。所以我们需要有一个功能,可以将它们管理起来,与该数据表的修改和删除操作关联起来,进行修改和删除操作后同步清除这些特殊缓存。

python开发QQ群:669058475    作者博客:http://www.cnblogs.com/EmptyFS/

View Code

 

  改造编辑记录实体方法

  下面改造调用例子(请查看login.py第35行附近)

  删除记录也进行一样的改造

 1     ##############################################################
 2     ### 更新用户信息到数据库 ###
 3     ##############################################################
 4     # 更新当前管理员最后登录时间、Ip与登录次数(字段说明,请看数据字典)
 5     fields = {
 6         'last_login_time': 'now()',
 7         'last_login_ip': string(ip),
 8         'login_count': 'login_count+1',
 9     }
10     # 写入数据库
11     _manager_logic.edit_model(manager_id, fields)

  然后我们再添加特殊缓存存储方法

  在编写时我们会发现,我们的ORM并没有直接判断记录是否存在的方法,只有一个用于获取指定条件记录数的方法。

 

 

    def set_model_for_cache(self, pk, value, time=43200):
        """更新存储在缓存中的数据库记录,缓存过期时间为12小时"""
        # 生成缓存key
        key = self.get_cache_key(pk)
        # 存储到nosql缓存中
        cache_helper.set(key, value, time)

  我们在开发时,除了通过主键id来获取记录实体以外,在有的数据表中,还会存在第二个主键,或多个主键的情况,我们需要通过这些主键来获取对应的记录实休,比如说管理员或用户表中的登录账号字段;订单表中的订单编码字段等。

 1     def del_relevance_cache(self):
 2         """删除关联缓存————将和数据表记录关联的,个性化缓存全部删除"""
 3         # 从nosql中读取全局缓存列表
 4         cache_list = cache_helper.get(self.__cache_list)
 5         # 清除已删除缓存列表
 6         cache_helper.delete(self.__cache_list)
 7         if cache_list:
 8             # 执行删除操作
 9             for cache in cache_list:
10                 cache_helper.delete(cache)

  如果要赋postgresql系统变量,如now(),直接像上面这样写就可以了

    def get_cache_key(self, pk):
        """获取缓存key值"""
        return ''.join((self.__table_name, '_', str(pk)))

  1.有没有替代可以实现的方法存在

 

  只需要将第7行到第10行替换对应的调用代码就可以了

 

 1 @delete('/api/product_class/<id:int>/')
 2 def callback(id):
 3     """
 4     删除指定记录
 5     """
 6     # 实例化product表操作类ProductLogic
 7     _product_logic = product_logic.ProductLogic()
 8     # 判断该分类是否已经被引用,是的话不能直接删除
 9     if _product_logic.exists('product_class_id=' + str(id)):
10         return web_helper.return_msg(-1, "该分类已被引用,请清除对该分类的绑定后再来删除")
11 
12     # 实例化product_class表操作类product_class_logic
13     _product_class_logic = product_class_logic.ProductClassLogic()
14     result = _product_class_logic.delete_model(id)
15     # 判断是否提交成功
16     if result:
17         return web_helper.return_msg(0, '成功')
18     else:
19         return web_helper.return_msg(-1, "删除失败")

  在改造时,我们只需要对删除与修改操作进行处理,对新增与查询操作不需要操作,因为新增的记录,它并在缓存中并不存在,所以不需要进行操作,而查询也不会改变数据内容,只有进行删除和修改操作时,才会变动数据内容,这时就需要更改缓存,让数据保持一致。

  我们可以改造为:

图片 3图片 4

 1 @delete('/api/product/<id:int>/')
 2 def callback(id):
 3     """
 4     删除指定记录
 5     """
 6     # 实例化product表操作类ProductLogic
 7     _product_logic = product_logic.ProductLogic()
 8     result = _product_logic.delete_model(id)
 9     # 判断是否提交成功
10     if result:
11         return web_helper.return_msg(0, '成功')
12     else:
13         return web_helper.return_msg(-1, "删除失败")

 

  首先是初始化产品逻辑层操作类,然后调用delete_model()这个方法就可以了

View Code

 

  在config文件夹中我们创建redis_config.py配置文件

  在写ORM模块时,我们已经对产品接口的分页查询、新增、修改、获取指定产品实体接口已经重构好了,还剩下删除接口未完成

#!/usr/bin/env python
# coding=utf-8

### redis缓存配置参数 ###
REDIS = {
    # 服务地址
    'server': '127.0.0.1',
    # 服务端口
    'post': 6379,
    # 服务密码
    'pwd': '',
    # 数据库序号
    'db': 1
}

  通过这个例子,大家在实际开发过程中,可以灵活的根据自己需要,来增加或改造对应的底层方法,积累你自己的底层框架代码,那么随着开发时间的增加,你开发起各种功能来就会越加得心应手了。

  对于缓存的操作,主要有设置缓存、获取缓存、删除缓存这三种操作,当然为了方便我们获取记录中指定字段值,我们可以增加读取指定字段值方法。

 

  1 #!/usr/bin/env python
  2 # coding=utf-8
  3 
  4 import redis
  5 
  6 from common import log_helper
  7 from config import redis_config
  8 
  9 # 设置redis配置参数
 10 _redis = redis_config.REDIS
 11 # 初始化Redis缓存链接
 12 r = None
 13 try:
 14     if not r:
 15         r = redis.Redis(host=_redis.get('server', ''),
 16                         port=_redis.get('post', ''),
 17                         db=_redis.get('db', ''),
 18                         password=_redis.get('pwd', ''),
 19                         socket_timeout=1,
 20                         socket_connect_timeout=1)
 21 except Exception as e:
 22     log_helper.info('连接redis出错:(' + str(_redis) + ')' + str(e.args))
 23     pass
 24 
 25 
 26 def set(key, value, time=86400):
 27     """
 28     写缓存
 29     :param key: 缓存key,字符串,不区分大小写
 30     :param value: 要存储的值
 31     :param time: 缓存过期时间(单位:秒),0=永不过期
 32     :return:
 33     """
 34     # 将key转换为小写字母
 35     key = str(key).lower()
 36     try:
 37         r.set(key, value, time)
 38     except Exception as e:
 39         log_helper.info('写缓存失败:key(' + key + ')' + str(e.args))
 40         pass
 41 
 42 
 43 def get(key):
 44     """
 45     读缓存
 46     :param key: 缓存key,字符串,不区分大小写
 47     :return:
 48     """
 49     # 将key转换为小写字母
 50     key = str(key).lower()
 51     try:
 52         value = r.get(key)
 53     except Exception as e:
 54         # log_helper.error('读缓存失败:key(' + key + ')' + str(e.args) + ' r:' + str(r) + ' _redis:' + str(_redis))
 55         value = None
 56 
 57     return _str_to_json(value)
 58 
 59 
 60 def push(key, value):
 61     """
 62     添加数据到队列头部
 63     :param key: 缓存key,字符串,不区分大小写
 64     :param value: 要存储的值
 65     """
 66     # 将key转换为小写字母
 67     key = str(key).lower()
 68     try:
 69         r.lpush(key, value)
 70     except Exception as e:
 71         log_helper.info('写缓存失败:key(' + key + ')' + str(e.args))
 72         pass
 73 
 74 
 75 def pop(key):
 76     """
 77     从缓存队列的后尾读取一条数据
 78     :param key: 缓存key,字符串,不区分大小写
 79     :return: 缓存数据
 80     """
 81     # 将key转换为小写字母
 82     key = str(key).lower()
 83     try:
 84         value = r.rpop(key)
 85     except Exception as e:
 86         log_helper.info('读取缓存队列失败:key(' + key + ')' + str(e.args))
 87         value = None
 88 
 89     return _str_to_json(value)
 90 
 91 
 92 def _str_to_json(value):
 93     """
 94     将缓存中读取出来的字符串转换成对应的数据、元组、列表或字典
 95     """
 96     if not value:
 97         return value
 98     # 否则直接转换
 99     try:
100         value = value.decode()
101         return eval(value)
102     except Exception as e:
103         print(e.args)
104         pass
105     # 否则直接输出字符串
106     return value
107 
108 
109 def delete(key):
110     """
111     删除缓存
112     :param key:缓存key,字符串,不区分大小写
113     :return:
114     """
115     # 将key转换为小写字母
116     key = str(key).lower()
117     try:
118         log_helper.info(str(r.delete(key)))
119     except Exception as e:
120         log_helper.info('Exception:' + str(e.args))
121         pass
122 
123 
124 def clear():
125     """
126     清空所有缓存
127     """
128     try:
129         r.flushdb()
130     except:
131         pass
 1 @delete('/api/product_class/<id:int>/')
 2 def callback(id):
 3     """
 4     删除指定记录
 5     """
 6     # 判断该分类是否已经被引用,是的话不能直接删除
 7     sql = """select count(*) as total from product where product_class_id=%s""" % (id,)
 8     # 读取记录
 9     result = db_helper.read(sql)
10     if result and result[0].get('total', -1) > 0:
11         return web_helper.return_msg(-1, "该分类已被引用,请清除对该分类的绑定后再来删除")
12 
13     # 编辑记录
14     sql = """delete from product_class where id=%s returning id"""
15     vars = (id,)
16     # 写入数据库
17     result = db_helper.write(sql, vars)
18     # 判断是否提交成功
19     if result:
20         return web_helper.return_msg(0, '成功')
21     else:
22         return web_helper.return_msg(-1, "删除失败")
 1     def edit(self, fields, wheres='', returning='', is_update_cache=True):
 2         """
 3         批量编辑数据库记录
 4         :param fields: 要更新的字段(字段名与值存储在字典中)
 5         :param wheres: 更新条件
 6         :param returning: 更新成功后,返回的字段名
 7         :param is_update_cache: 是否同步更新缓存
 8         :return:
 9         """
10         ### 拼接sql语句 ###
11         # 拼接字段与值
12         field_list = [key + ' = %(' + key + ')s' for key in fields.keys()]
13         # 设置sql拼接字典
14         parameter = {
15             'table_name': self.__table_name,
16             'pk_name': self.__pk_name,
17             'field_list': ','.join(field_list)
18         }
19         # 如果存在更新条件,则将条件添加到sql拼接更换字典中
20         if wheres:
21             parameter['wheres'] = ' where ' + wheres
22         else:
23             parameter['wheres'] = ''
24 
25         # 如果有指定返回参数,则添加
26         if returning:
27             parameter['returning'] = ', ' + returning
28         else:
29             parameter['returning'] = ''
30 
31         # 生成sql语句
32         sql = "update %(table_name)s set %(field_list)s %(wheres)s returning %(pk_name)s %(returning)s" % parameter
33         sql = sql % fields
34 
35         result = self.execute(sql)
36         if result:
37             # 判断是否删除对应的缓存
38             if is_update_cache:
39                 # 循环删除更新成功的所有记录对应的缓存
40                 for model in result:
41                     self.del_model_for_cache(model.get('id', 0))
42         return result

  具体操作需要大家自己多debug,多测试使用才知道怎么应用到真实项目中。

    def get_model_for_cache(self, pk):
        """从缓存中读取数据库记录"""
        # 生成缓存key
        key = self.get_cache_key(pk)
        # 从缓存中读取数据库记录
        result = cache_helper.get(key)
        # 缓存中不存在记录,则从数据库获取
        if not result:
            result = self.get_model_for_pk(pk)
            self.set_model_for_cache(pk, result)
        if result:
            return result
        else:
            return {}

  细心的朋友会发现,ORM模块的缓存部分,多了一个get_model_for_cache_of_where()方法,下面我来说明一下它的用途。

  然后我们再增加一个读取指定记录字段值的方法

  当你习惯这种写法以后,你会发现实现各个接口会变得非常的简单与方便,开发速度比之前也提升了很多

  1 #!/usr/bin/env python
  2 # coding=utf-8
  3 
  4 from common import db_helper, cache_helper
  5 
  6 
  7 class LogicBase():
  8     """逻辑层基础类"""
  9 
 10     def __init__(self, db, is_output_sql, table_name, column_name_list='*', pk_name='id'):
 11         """类初始化"""
 12         # 数据库参数
 13         self.__db = db
 14         # 是否输出执行的Sql语句到日志中
 15         self.__is_output_sql = is_output_sql
 16         # 表名称
 17         self.__table_name = str(table_name).lower()
 18         # 查询的列字段名称,*表示查询全部字段,多于1个字段时用逗号进行分隔,除了字段名外,也可以是表达式
 19         self.__column_name_list = str(column_name_list).lower()
 20         # 主健名称
 21         self.__pk_name = str(pk_name).lower()
 22         # 缓存列表
 23         self.__cache_list = self.__table_name + '_cache_list'
 24 
 25     #####################################################################
 26     ### 执行Sql ###
 27 
 28     def select(self, sql):
 29         """执行sql查询语句(select)"""
 30         with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
 31             # 执行sql语句
 32             result = db.execute(sql)
 33             if not result:
 34                 result = []
 35         return result
 36 
 37     def execute(self, sql):
 38         """执行sql语句,并提交事务"""
 39         with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
 40             # 执行sql语句
 41             result = db.execute(sql)
 42             if result:
 43                 db.commit()
 44             else:
 45                 result = []
 46         return result
 47 
 48     def copy(self, values, columns):
 49         """批量更新数据"""
 50         with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
 51             # 执行sql语句
 52             result = db.copy(values, self.__table_name, columns)
 53         return result
 54 
 55     def get_model(self, wheres):
 56         """通过条件获取一条记录"""
 57         # 如果有条件,则自动添加where
 58         if wheres:
 59             wheres = ' where ' + wheres
 60 
 61         # 合成sql语句
 62         sql = "select %(column_name_list)s from %(table_name)s %(wheres)s" % 
 63               {'column_name_list': self.__column_name_list, 'table_name': self.__table_name, 'wheres': wheres}
 64         # 初化化数据库链接
 65         result = self.select(sql)
 66         if result:
 67             return result[0]
 68         return {}
 69 
 70     def get_model_for_pk(self, pk, wheres=''):
 71         """通过主键值获取数据库记录实体"""
 72         if not pk:
 73             return {}
 74         # 组装查询条件
 75         wheres = '%s = %s' % (self.__pk_name, str(pk))
 76 
 77         return self.get_model(wheres)
 78 
 79     def get_value(self, column_name, wheres=''):
 80         """
 81         获取指定条件的字段值————多于条记录时,只取第一条记录
 82         :param column_name: 单个字段名,如:id
 83         :param wheres: 查询条件
 84         :return: 7 (指定的字段值)
 85         """
 86         if not column_name:
 87             return None
 88         elif wheres:
 89             wheres = ' where ' + wheres
 90 
 91         sql = 'select %(column_name)s from %(table_name)s %(wheres)s limit 1' % 
 92               {'column_name': column_name, 'table_name': self.__table_name, 'wheres': wheres}
 93         result = self.select(sql)
 94         # 如果查询成功,则直接返回记录字典
 95         if result:
 96             return result[0].get(column_name)
 97 
 98     def get_value_list(self, column_name, wheres=''):
 99         """
100         获取指定条件记录的字段值列表
101         :param column_name: 单个字段名,如:id
102         :param wheres: 查询条件
103         :return: [1,3,4,6,7]
104         """
105         if not column_name:
106             column_name = self.__pk_name
107         elif wheres:
108             wheres = ' where ' + wheres
109 
110         sql = 'select array_agg(%(column_name)s) as list from %(table_name)s %(wheres)s' % 
111               {'column_name': column_name, 'table_name': self.__table_name, 'wheres': wheres}
112         result = self.select(sql)
113         # 如果查询失败或不存在指定条件记录,则直接返回初始值
114         if result and isinstance(result, list):
115             return result[0].get('list')
116         else:
117             return []
118 
119     def add_model(self, fields, returning=''):
120         """新增数据库记录"""
121         ### 拼接sql语句 ###
122         # 初始化变量
123         key_list = []
124         value_list = []
125         # 将传入的字典参数进行处理,把字段名生成sql插入字段名数组和字典替换数组
126         # PS:字符串使用字典替换参数时,格式是%(name)s,这里会生成对应的字串
127         # 比如:
128         #   传入的字典为: {'id': 1, 'name': '名称'}
129         #   那么生成的key_list为:'id','name'
130         #   而value_list为:'%(id)s,%(name)s'
131         #   最终而value_list为字符串对应名称位置会被替换成相应的值
132         for key in fields.keys():
133             key_list.append(key)
134             value_list.append('%(' + key + ')s')
135         # 设置sql拼接字典,并将数组(lit)使用join方式进行拼接,生成用逗号分隔的字符串
136         parameter = {
137             'table_name': self.__table_name,
138             'pk_name': self.__pk_name,
139             'key_list': ','.join(key_list),
140             'value_list': ','.join(value_list)
141         }
142         # 如果有指定返回参数,则添加
143         if returning:
144             parameter['returning'] = ', ' + returning
145         else:
146             parameter['returning'] = ''
147 
148         # 生成可以使用字典替换的字符串
149         sql = "insert into %(table_name)s (%(key_list)s) values (%(value_list)s) returning %(pk_name)s %(returning)s" % parameter
150         # 将生成好的字符串替字典参数值,生成最终可执行的sql语句
151         sql = sql % fields
152 
153         result = self.execute(sql)
154         if result:
155             return result[0]
156         return {}
157 
158     def edit(self, fields, wheres='', returning='', is_update_cache=True):
159         """
160         批量编辑数据库记录
161         :param fields: 要更新的字段(字段名与值存储在字典中)
162         :param wheres: 更新条件
163         :param returning: 更新成功后,返回的字段名
164         :param is_update_cache: 是否同步更新缓存
165         :return:
166         """
167         ### 拼接sql语句 ###
168         # 拼接字段与值
169         field_list = [key + ' = %(' + key + ')s' for key in fields.keys()]
170         # 设置sql拼接字典
171         parameter = {
172             'table_name': self.__table_name,
173             'pk_name': self.__pk_name,
174             'field_list': ','.join(field_list)
175         }
176         # 如果存在更新条件,则将条件添加到sql拼接更换字典中
177         if wheres:
178             parameter['wheres'] = ' where ' + wheres
179         else:
180             parameter['wheres'] = ''
181 
182         # 如果有指定返回参数,则添加
183         if returning:
184             parameter['returning'] = ', ' + returning
185         else:
186             parameter['returning'] = ''
187 
188         # 生成sql语句
189         sql = "update %(table_name)s set %(field_list)s %(wheres)s returning %(pk_name)s %(returning)s" % parameter
190         sql = sql % fields
191 
192         result = self.execute(sql)
193         if result:
194             # 判断是否删除对应的缓存
195             if is_update_cache:
196                 # 循环删除更新成功的所有记录对应的缓存
197                 for model in result:
198                     self.del_model_for_cache(model.get('id', 0))
199                 # 同步删除与本表关联的缓存
200                 self.del_relevance_cache()
201         return result
202 
203     def edit_model(self, pk, fields, wheres='', returning=''):
204         """编辑单条数据库记录"""
205         if not pk:
206             return {}
207         elif wheres:
208             wheres = self.__pk_name + ' = ' + str(pk) + ' and ' + wheres
209         else:
210             wheres = self.__pk_name + ' = ' + str(pk)
211 
212         return self.edit(fields, wheres, returning)
213 
214     def delete(self, wheres='', returning='', is_update_cache=True):
215         """
216         批量删除数据库记录
217         :param wheres: 删除条件
218         :param returning: 删除成功后,返回的字段名
219         :param is_update_cache: 是否同步更新缓存
220         :return:
221         """
222         # 如果存在条件
223         if wheres:
224             wheres = ' where ' + wheres
225 
226         # 如果有指定返回参数,则添加
227         if returning:
228             returning = ', ' + returning
229 
230         # 生成sql语句
231         sql = "delete from %(table_name)s %(wheres)s returning %(pk_name)s %(returning)s" % 
232               {'table_name': self.__table_name, 'wheres': wheres, 'pk_name': self.__pk_name, 'returning': returning}
233         result = self.execute(sql)
234         if result:
235             # 同步删除对应的缓存
236             if is_update_cache:
237                 for model in result:
238                     self.del_model_for_cache(model.get('id', 0))
239                 # 同步删除与本表关联的缓存
240                 self.del_relevance_cache()
241         return result
242 
243     def delete_model(self, pk, wheres='', returning='', is_update_cache=True):
244         """删除单条数据库记录"""
245         if not pk:
246             return {}
247         elif wheres:
248             wheres = self.__pk_name + ' = ' + str(pk) + ' and ' + wheres
249         else:
250             wheres = self.__pk_name + ' = ' + str(pk)
251 
252         return self.delete(wheres, returning)
253 
254     def get_list(self, column_name_list='', wheres='', page_number=None, page_size=None, orderby=None, table_name=None):
255         """
256         获取指定条件的数据库记录集
257         :param column_name_list:      查询字段
258         :param wheres:      查询条件
259         :param page_number:   分页索引值
260         :param page_size:    分页大小, 存在值时才会执行分页
261         :param orderby:     排序规则
262         :param table_name:     查询数据表,多表查询时需要设置
263         :return: 返回记录集总数量与分页记录集
264             {'records': 0, 'total': 0, 'page': 0, 'rows': []}
265         """
266         # 初始化输出参数:总记录数量与列表集
267         data = {
268             'records': 0,  # 总记录数
269             'total': 0,  # 总页数
270             'page': 1,  # 当前页面索引
271             'rows': [],  # 查询结果(记录列表)
272         }
273         # 初始化查询数据表名称
274         if not table_name:
275             table_name = self.__table_name
276         # 初始化查询字段名
277         if not column_name_list:
278             column_name_list = self.__column_name_list
279         # 初始化查询条件
280         if wheres:
281             # 如果是字符串,表示该查询条件已组装好了,直接可以使用
282             if isinstance(wheres, str):
283                 wheres = 'where ' + wheres
284             # 如果是list,则表示查询条件有多个,可以使用join将它们用and方式组合起来使用
285             elif isinstance(wheres, list):
286                 wheres = 'where ' + ' and '.join(wheres)
287         # 初始化排序
288         if not orderby:
289             orderby = self.__pk_name + ' desc'
290         # 初始化分页查询的记录区间
291         paging = ''
292 
293         with db_helper.PgHelper(self.__db, self.__is_output_sql) as db:
294             #############################################################
295             # 判断是否需要进行分页
296             if not page_size is None:
297                 ### 执行sql,获取指定条件的记录总数量
298                 sql = 'select count(1) as records from %(table_name)s %(wheres)s ' % 
299                       {'table_name': table_name, 'wheres': wheres}
300                 result = db.execute(sql)
301                 # 如果查询失败或不存在指定条件记录,则直接返回初始值
302                 if not result or result[0]['records'] == 0:
303                     return data
304 
305                 # 设置记录总数量
306                 data['records'] = result[0].get('records')
307 
308                 #########################################################
309                 ### 设置分页索引与页面大小 ###
310                 if page_size <= 0:
311                     page_size = 10
312                 # 计算总分页数量:通过总记录数除于每页显示数量来计算总分页数量
313                 if data['records'] % page_size == 0:
314                     page_total = data['records'] // page_size
315                 else:
316                     page_total = data['records'] // page_size + 1
317                 # 判断页码是否超出限制,超出限制查询时会出现异常,所以将页面索引设置为最后一页
318                 if page_number < 1 or page_number > page_total:
319                     page_number = page_total
320                 # 记录总页面数量
321                 data['total'] = page_total
322                 # 记录当前页面值
323                 data['page'] = page_number
324                 # 计算当前页面要显示的记录起始位置(limit指定的位置)
325                 record_number = (page_number - 1) * page_size
326                 # 设置查询分页条件
327                 paging = ' limit ' + str(page_size) + ' offset ' + str(record_number)
328             #############################################################
329 
330             ### 按条件查询数据库记录
331             sql = "select %(column_name_list)s from %(table_name)s %(wheres)s order by %(orderby)s %(paging)s" % 
332                   {'column_name_list': column_name_list,
333                    'table_name': table_name,
334                    'wheres': wheres,
335                    'orderby': orderby,
336                    'paging': paging}
337             result = db.execute(sql)
338             if result:
339                 data['rows'] = result
340                 # 不需要分页查询时,直接在这里设置总记录数
341                 if page_size is None:
342                     data['records'] = len(result)
343 
344         return data
345 
346     def get_count(self, wheres=''):
347         """获取指定条件记录数量"""
348         if wheres:
349             wheres = ' where ' + wheres
350         sql = 'select count(1) as total from %(table_name)s %(wheres)s ' % 
351               {'table_name': self.__table_name, 'wheres': wheres}
352         result = self.select(sql)
353         # 如果查询存在记录,则返回true
354         if result:
355             return result[0].get('total')
356         return 0
357 
358     def get_sum(self, fields, wheres):
359         """获取指定条件记录数量"""
360         sql = 'select sum(%(fields)s) as total from %(table_name)s where %(wheres)s ' % 
361               {'table_name': self.__table_name, 'wheres': wheres, 'fields': fields}
362         result = self.select(sql)
363         # 如果查询存在记录,则返回true
364         if result and result[0].get('total'):
365             return result[0].get('total')
366         return 0
367 
368     def get_min(self, fields, wheres):
369         """获取该列记录最小值"""
370         sql = 'select min(%(fields)s) as min from %(table_name)s where %(wheres)s ' % 
371               {'table_name': self.__table_name, 'wheres': wheres, 'fields': fields}
372         result = self.select(sql)
373         # 如果查询存在记录,则返回true
374         if result and result[0].get('min'):
375             return result[0].get('min')
376 
377     def get_max(self, fields, wheres):
378         """获取该列记录最大值"""
379         sql = 'select max(%(fields)s) as max from %(table_name)s where %(wheres)s ' % 
380               {'table_name': self.__table_name, 'wheres': wheres, 'fields': fields}
381         result = self.select(sql)
382         # 如果查询存在记录,则返回true
383         if result and result[0].get('max'):
384             return result[0].get('max')
385 
386             #####################################################################
387 
388 
389     #####################################################################
390     ### 缓存操作方法 ###
391 
392     def get_cache_key(self, pk):
393         """获取缓存key值"""
394         return ''.join((self.__table_name, '_', str(pk)))
395 
396     def set_model_for_cache(self, pk, value, time=43200):
397         """更新存储在缓存中的数据库记录,缓存过期时间为12小时"""
398         # 生成缓存key
399         key = self.get_cache_key(pk)
400         # 存储到nosql缓存中
401         cache_helper.set(key, value, time)
402 
403     def get_model_for_cache(self, pk):
404         """从缓存中读取数据库记录"""
405         # 生成缓存key
406         key = self.get_cache_key(pk)
407         # 从缓存中读取数据库记录
408         result = cache_helper.get(key)
409         # 缓存中不存在记录,则从数据库获取
410         if not result:
411             result = self.get_model_for_pk(pk)
412             self.set_model_for_cache(pk, result)
413         if result:
414             return result
415         else:
416             return {}
417 
418     def get_value_for_cache(self, pk, column_name):
419         """获取指定记录的字段值"""
420         return self.get_model_for_cache(pk).get(column_name)
421 
422     def del_model_for_cache(self, pk):
423         """删除缓存中指定数据"""
424         # 生成缓存key
425         key = self.get_cache_key(pk)
426         # log_helper.info(key)
427         # 存储到nosql缓存中
428         cache_helper.delete(key)
429 
430     def add_relevance_cache_in_list(self, key):
431         """将缓存名称存储到列表里————主要存储与记录变更关联的"""
432         # 从nosql中读取全局缓存列表
433         cache_list = cache_helper.get(self.__cache_list)
434         # 判断缓存列表是否有值,有则进行添加操作
435         if cache_list:
436             # 判断是否已存储列表中,不存在则执行添加操作
437             if not key in cache_list:
438                 cache_list.append(key)
439                 cache_helper.set(self.__cache_list, cache_list)
440         # 无则直接创建全局缓存列表,并存储到nosql中
441         else:
442             cache_list = [key]
443             cache_helper.set(self.__cache_list, cache_list)
444 
445     def del_relevance_cache(self):
446         """删除关联缓存————将和数据表记录关联的,个性化缓存全部删除"""
447         # 从nosql中读取全局缓存列表
448         cache_list = cache_helper.get(self.__cache_list)
449         # 清除已删除缓存列表
450         cache_helper.delete(self.__cache_list)
451         if cache_list:
452             # 执行删除操作
453             for cache in cache_list:
454                 cache_helper.delete(cache)
455 
456     #####################################################################

  如果前面代码有认真学习的小伙伴看到这段代码,要改成ORM方式应该很容易实现了

  而清理缓存,我们只需要将缓存内容直接删除就可以了,因为执行更新以后,返回的记录实体没有设置为*时,只返回主键id,直接设置的话会造成缓存数据丢失细节的问题,另外,我们执行更新以后,该记录也不一定还会被读取出来。

1 ##############################################################
2     ### 更新用户信息到数据库 ###
3     ##############################################################
4     # 更新当前管理员最后登录时间、Ip与登录次数(字段说明,请看数据字典)
5     sql = """update manager set last_login_time=%s, last_login_ip=%s, login_count=login_count+1 where id=%s"""
6     # 组合更新值
7     vars = ('now()', ip, manager_id,)
8     # 写入数据库
9     db_helper.write(sql, vars)

  首先我们需要导入cache_helper

  一般来说,我们在开发时发现ORM没有自己想要的方法时,我们需要做以下思考:

 

  前面ORM模块我们已经完成了开发,接下来要做的就是对项目代码进行重构了。因为对底层数据库操作模块(db_helper.py)进行了改造,之前项目的接口代码全都跑不起来了。

本文由金沙官网线上发布于编程,转载请注明出处:我的第一个python web开发框架(31)——定制ORM(

您可能还会对下面的文章感兴趣: