ORM反射数据表到实体类--好用到哭泣
前言
为了避免sql注入或白盒漏洞问题,同时也是为了方便数据底层操作,工作中会尽量避免使用原生sql,而选择ORM。python中常用的有sqlalchemy库。该库的使用可以学习参考https://www.cnblogs.com/liu-yao/p/5342656.html
在使用过程中会发现需要自行创建一个class去映射数据表结构,然后通过操作class进行数据的增删改查等一系列操作。这个其实已经能满足日常的使用,但是在工作中我们常会选择数据分离,分工明确, 比如我就遇到需要设计底层DAL,提供统一的数据接口给所有业务,我们可以选择让业务线把设计好的表结构或者创建语句统一给我们,然后保存一个py文件去维护这些表信息,将表结构映射成model。
这种做法有以下几个问题:
- 修改表结构信息,需要动态修改py文件
- 业务线修改表结构可能不能完全同步到DAL层,导致业务逻辑出现瘫痪
- 对于线上环境,每次修改都需要重新部署上线,这是一个浪费人力和时间的事情,而且不能保证完全不出错。
基于以上考虑,我们会考虑,如何动态获取数据库中表结构的变化,这就是所谓的数据表反射成实体类(起初不知道这个概念,只能查询资料:如何动态生成数据表的model类)。
数据表反射成实体类
方法一: sqlacodegen命令
查询资料,首先看到的是使用sqlacodegen方法,建议参考博客:起初使用这种方法在本地确实可以方便的生成所有实体类,并生成model文件,然后获取文件中的所有class,最后我们就可以通过表名获取到对应的实体类,然后进行ORM操作
def get_classes(module_name=storage.db_models) -> dict:
# 获取db_models文件 中所有类对象
classes = []
table = {}
file = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/storage/' + 'db_models.py'
if not os.path.exists(file):
return table
ret = inspect.getmembers(module_name, inspect.isclass)
try:
for name, class_ in ret:
classes.append(class_)
if '__tablename__' in dir(class_):
table[class_.__tablename__] = class_
except Exception as e:
logging(e, exc_info=True)
return table
缺点:
- 通过ip+port的形式连接数据库,在本地可操作,线上不一定会被允许,例如我们公司不允许使用ip+port的形式连接,封装了自己的mysql库,导致上线失败
- 需要定时的去运行sqlacodegen命令,保证获取的model文件是最新的
方法二: SQLAlchemy 反射已有表
强烈推荐使用这种方法
真是宝藏方法,解决了很大一麻烦,具体可以参考这篇博客,里面有三种方法https://www.jianshu.com/p/f6527ab2428d, 下面展示自己写的伪代码:亲测是有效的,可以动态感知数据表结构变化
def create_model(self):
"""反射数据表为实体类"""
engine = SQLAlchemyEngine()._engine # 创建数据库引擎,可以根据具体情况操作,不用担心线上线下环境影响,和普通连接数据库创建引擎方法相同
Base = automap_base()
Base.prepare(engine, reflect=True)
Base.classes.keys() # 获取所有的对象名
return Base.classes
def test():
table = self.create_table()
table_name = 'test_example‘ # 数据库表名
model = table[table_name]
ret = s.query(model).filter_by(**condition).update(data) # 进行ORM操作
通过这次事件,以后即使不做DAL层,只写业务代码,也会选择这种方法,不会呆板地维护自己的model类了,虽然对于熟悉业务的个人来说改造比较方便成本不大,但是如果进行业务交接就很麻烦了,保持一个原则:能动态感知的尽量使用动态方法感知,能少改动代码的少改动,能不重新上线的不上线。