Whoosy's Blog

藏巧于拙 用晦而明 寓清于浊 以屈为伸

0%

自定义字段的几种实现方式

在web开发中,我们经常会遇到项目中很多对表单进行自定义,比如说saas应用针对用户自定义表单字段名称,自定义列表名称。 还有更高级自定义,比如说自定义的模块,表单、字段、字段类型、流程等自定义。

提供自定义也是一个系统扩展性的体现,自定义功能的强大自然能适应更多的用户场景。

接下来我们就看看自定义的实现方案通常都有哪些方式。

常见的自定义字段的实现方式分为四种由简到繁,扩展性、复杂性也是逐渐增强的,每个方式各有优劣解决的场景也有所不同,具体往下看。

动态列式存储自定义字段

模型如下

ID Name Ext1(性别) Ext2(地区) Ext3(手机号) Ext4(WECAHT)
1 韩梅梅 河南 13700000000
2 李雷 北京 abc

优点

  1. 查询效率高。
  2. 支持关联查询。

缺点

  1. 需要动态变更表结构,在生产环境中安全性太低。
  2. 扩展能力一般,有上限。
  3. 浪费资源,比如说有20个扩展字段,一行只用到2个,其余的18个都要存储null来浪费空间。
  4. 能解决的场景比较有限。

使用关系型数据库EAV模型进行自定义字段存储

在EAV模型中,对象存储在一个表中会有三种属性描述:实体、属性、值。实体表示一条数据,属性表示实体的具有的特征,值表示实体的特征值。此外,属性可以单独存放在另一个表中,通过外键关联对象与属性的关系。

这种模型带来了数据的灵活性,增加对象的属性不需要动态增加数据表的字段。但是EAV表也有较大的性能问题。通常,EAV表带来的一个问题是当查找多个字段时,需要进行关联查询join, 这样的查询效率比较低。

我们常用的行模型(纵向)存储就是EAV模型实现的一种方式

模型如下

人员表

ID Name
1 韩梅梅
2 李雷

扩展映射(Entities)

Entity Attribute Value
1 sex(性别)
2 sex(性别)
1 region(地区) 河南
2 region(地区) 北京
1 QQ 123456
2 WECHAT abc

优点

  1. 扩展能力强
  2. 理论上增加字段无上限
  3. 可以支持几乎所有的自定义字段类型的需求

缺点

  1. 关联查询效率低下
  2. 需要维护自定义字段与值的关系表

Json格式存储自定义字段

目前,市面上主流的关系型数据库几乎都已经添加对JSON和JSONB的字段的存储,这一特性的升级,增加了非关系型数据库的灵活性和可扩展性,而且保留了关系型数据库在事务处理、复杂对象的存储查询方面的优势,开发和使用也非常简单。

json格式非常丰富,在描述自定义字段的这方面比较适合,可以把一行多列的数据压缩到一个json text内,也比较节省空间,json格式可以无限扩展,还可支持多个自定义字段有不同的格式。

模型如下

ps.支持以下两种存储方式

​ (1)

ID Name Content
1 韩梅梅 {“age”:18, “region”: “上海”}
2 李雷 {“age”:18, “WECHAT”:123456}

此方式业务表中只存自定义字段的值, 字段结构(值类型、是否为null等)可存在另外表中,根据业务需求来定。参考专利: 自定义字段的一种实现方式

​ (2)

ID Name Content
1 韩梅梅 [{“label”:”年龄”, “filedName”:”age”, “value”: 18}, {“label”: “地区”, “filedName”: “region”, “value”: “上海”}]
2 李雷 [{“label”:”年龄”, “filedName”:”age”, “value”: 18}, {“label”: “微信号”, “filedName”: “WECHAT”, “value”: 123456}]

优点

  1. 扩展能力强
  2. 理论上无上限
  3. 可以支持几乎所有自定义字段的需求
  4. 无需维护自定义字段与值的关系

缺点

  1. 数据库需要支持 JSON type, 不建议使用text(解析慢,存储空间大)
  2. 自定义字段不支持与其他表相同字段进行关联查询
  3. 自定义字段检索需要通过其他方式,例如搜索引擎、cast类型转化、特殊函数json_extract()等

数据库对Json格式支持情况


数据库对Json类型的支持(最新版本):

  1. Mysql
  2. PostgreSQL (json与jsonb的区别)
  3. MongoDB

数据库对Json类型的检索

  1. Mysql: 支持索引,通过虚拟列的功能可以对JSON中部分的数据进行索引。(比PG和MongoDB弱一些,通过json_extract()函数做一些简单查询)
  2. PostgreSQL: 支持检索,可以复杂查询
  3. MongoDB: 支持检索,可以复杂查询(支持map reduce)

ORM框架对Json类型的支持:

  1. sqlAlchemy支持postgresql数据库的对json和jsonb格式字段映射,并支持针对json和jsonb格式的查询。查看orm示例

使用sqlalchemy对jsonb格式的存储与检索

  1. 定义model模型
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
from sqlalchemy import Column, Integer, String, cast
from sqlalchemy import create_engine
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import Mutable
from sqlalchemy.orm import sessionmaker

# ***************************
engine = create_engine('postgresql://t_pg:t_pg@127.0.0.1:5432/t_pg', echo=False)
Base = declarative_base(bind=engine)
DBSession = sessionmaker(bind=engine)


class MutableDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
"Convert plain dictionaries to MutableDict."

if not isinstance(value, MutableDict):
if isinstance(value, dict):
return MutableDict(value)

# this call will raise ValueError
return Mutable.coerce(key, value)
else:
return value

def __setitem__(self, key, value):
"Detect dictionary set events and emit change events."

dict.__setitem__(self, key, value)
self.changed()

def __delitem__(self, key):
"Detect dictionary del events and emit change events."

dict.__delitem__(self, key)
self.changed()


class MyDataClass1(Base):
__tablename__ = 'my_data1'
id = Column(Integer, primary_key=True)
data = Column(MutableDict.as_mutable(JSONB))
name = Column(String(50))

def __repr__(self):
return self.name

2. 增加对象

1
2
3
4
5
6
7
8
9
if __name__ == '__main__':
Base.metadata.drop_all()
Base.metadata.create_all()
session = DBSession()
# 增加对象
m1 = MyDataClass1(data={'value1': 'foo1'}, name='xiaohong')
session.add(m1)
session.commit()
# #######session 提交后,data 可以关联到 query

3. 查看数据库存储

id name data
1 xiaohong {“value1”: “foo1”}

4. 对自定义字段进行检索

1
2
3
4
5
6
7
8
9
query_obj = session.query(MyDataClass1).filter(
MyDataClass1.data['value1'] == cast('foo1', JSONB)).first()
print(query_obj.name)
print(query_obj.data)

--------------------------------------------
# 输出结果
xiaohong
{'value1': 'foo1'}

MongoDB存储自定义字段

前面三种都是基于关系型数据库实现自定义存储方式,而利用mongodb天然支持对json格式的存储特性也可以实现自定义字段的存储。

优点

  1. 支持对json格式的存储
  2. 不需要提前建表,存储简单
  3. 易扩展,大数据量的高可用性

缺点

  1. 复杂对象的存储查询困难
  2. 事务方面处理不足
  3. 不适用于关系性强、业务逻辑复杂的系统
  4. 系统开发成本加大