需求
在业务中, entity实体之间难免出现继承关系,映射到库表时如何设计?
下面将介绍这几种设计方案并对比优缺点:
- EAV表(entity-Attribute-Value)
- 单表继承
- 实体表继承
- 类表继承
- 半结构化设计
(技术选型时需要结合具体业务分析再决定方案)
设计方案
EAV表设计
简介
EAV就是entity-Attribute-Value的意思(实体-属性-值),非结构化存储, 看图表:
user表:
id | attr_name | attr_value |
---|---|---|
1 | username | peter |
1 | gender | 男 |
1 | birthday | 2000-01-01 |
1 | age | 19 |
… | … | … |
比如想获取peter的用户信息:
select
*
from
user
where
id in (select id from user where attr_name = 'peter');
需要查多条记录到内存再慢慢组装。
这种存取方式的优缺点:
优势
- 灵活,增删字段不需要修改库表结构。不管entity是否存在继承关系,都可以使用EAV方式存储。
- 库表设计简单(id、attr_name、value)
劣势
- 对象的属性名没有约束(mysql没法限定用户名就叫username,业务可能不小心存为userName)
- 属性值的类型无法约束(age没法限定是int类型,业务可能不小心存了个‘abc’进去)
- 获取单个对象数据繁琐(查询一个完整对象,需要获取多条记录,还不能直接映射为User对象)
- 无法使用完整性检查特性(没法使用外键)
如果业务在新增属性时不需要修改库表结构,可能数据库采用的就是EAV设计。
(一般业务都建议使用结构化库表设计,毕竟EAV设计的缺点比较多,但如果业务的属性频繁变化,比如埋点事件上报等,可以考虑这种方案。)
单表继承
简介
将entity继承体系的所有属性都存储到一个表中,
实体属性如下:
库表设计如下:
user表:
id | username | password | age | subject | student_number |
---|---|---|---|---|---|
1 | teacher1 | xxx | 40 | 数学 | |
2 | student1 | xxx | 10 | 12341234 |
优势
- 库表设计简单
- 不需要链表查询即可获取到子类的完整信息
- 规避了EAV设计的很多缺陷
劣势
- 添加子类的属性时需要修改user表(锁表,数据量大时影响很大)
- 对于子类,表中出现无关的属性,比如教师的行出现了student_number, 学生的行出现了subject
使用场景:
继承体系中子类属性较少的情况。比如可预见的时间内子类的属性都比较少时可以使用这种方式,毕竟查询简单,不需要联表查询。
实体表继承
简介
实体的属性是完整的,实体表继承,就是说每个实体都对应一个完整的表。
比如用户继承体系中:
那么库表中,teacher表拥有完整的属性。student表也拥有完整属性,如图:
这种设计的优缺点:
优势
- 获取完整对象不需要联表查询
- 表中没有无关属性(跟单表继承的对比)
劣势
- 在基类添加属性时需要修改多个表(比如在User类添加birthday属性,则需要在teacher/student表都添加column)
- 表的结构松散,看不出类的继承关系
类表继承
简介
类的体系结构如下, 那么类表继承的表结构也如下,就是将类的继承结构映射到库表上。
那么在库表设计时也有三张表:user表、teacher表、student表(teacher表和student表有外键),如下:
优势
- 库表的层次结构清晰,库表直接反应了继承关系
- 为子类添加属性时不需要修改基类表(user表),为基类添加属性时不需要同时为多个表添加column
- 查询teacher、student的基类信息时不需要查询多个表(对比实体表继承方案)
劣势
4. 获取对象完整数据需要联表查询(在表数据量大时联表查询性能差)
半结构化设计
简介
使用一张表存储整个继承体系,但是基类的每个属性映射到表的对应column,子类的所有属性则使用一个json/xml类型的column来存储。举例:
对于下面的类:
设计库表如下:
other_properties字段,对于teacher对象来说存{subject=english}, 对于student对象存:{student_number=123}.
那么在查询时将整条记录查询到内存后再转为完整对象。
优势
- 可扩展性强,添加子类属性时不需要添加column,添加基类属性时才会添加column
- 查询简单,每行对应一个对象的完整信息,不需要联表查询
劣势
- 非结构化部分依旧有EAV设计的问题
- 对象的属性名没有约束(mysql没法限定用户名就叫username,业务可能不小心存为userName)
- 属性值的类型无法约束(age没法限定是int类型,业务可能不小心存了个‘abc’进去)
- 获取单个对象数据繁琐(查询一个完整对象,需要获取多条记录,还不能直接映射为User对象)
- 无法使用完整性检查特性(没法使用外键)
- 对于非结构化部分无法使用DB的聚合函数(比如sum、count等)
总结
欢迎点评和点赞…