序
今天跟大家主要是分享一个设计思路:树形数据,扩展属性存储、展示设计。
一、树形、扩展属性关系说明
我这里是将空间、楼栋、单元、楼层、房5类数据抽取相同属性、可一致的字段到一张表,设计为树形。以属性字段data_type区分数据类型。表结构:
CREATE TABLE `space_reach_house_info` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`data_type` int DEFAULT NULL COMMENT '数据类型:1空间,2楼栋,3单元,4楼层,5房',
`data_status` int DEFAULT NULL COMMENT '数据状态:1未发布,2已发布',
`house_type` int DEFAULT NULL COMMENT '房源类型,1分布式公寓,2集中式公寓',
`name` varchar(300) DEFAULT NULL COMMENT '名称',
`customize_name` varchar(300) DEFAULT NULL COMMENT '自定义名称',
`code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '编码',
`parent_id` bigint DEFAULT NULL COMMENT '父级id',
`parent_ids` varchar(4000) DEFAULT NULL COMMENT '父级ids,以,隔开',
`data_level` int DEFAULT NULL COMMENT '数据层级',
`sort_num` int DEFAULT NULL COMMENT '排序号',
`subject_id` int DEFAULT NULL COMMENT '项目id',
`org_id` int DEFAULT NULL COMMENT '公司id',
`longitude` double(10,6) DEFAULT NULL COMMENT '经度',
`latitude` double(10,6) DEFAULT NULL COMMENT '维度',
`memo` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
`principal_id` bigint DEFAULT NULL COMMENT '负责人id',
`principal_mobile` varchar(20) DEFAULT NULL COMMENT '负责人电话',
`create_by` int DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` int DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` int DEFAULT NULL COMMENT '是否删除,0否,1是',
PRIMARY KEY (`id`),
KEY `org_id_idx` (`org_id`) USING BTREE COMMENT '公司id索引',
KEY `subject_id_idx` (`subject_id`) USING BTREE COMMENT '项目id索引',
KEY `parent_id_idx` (`parent_id`) USING BTREE COMMENT '父级id索引',
KEY `data_type_idx` (`data_type`) USING BTREE COMMENT '数据类型索引'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='空间以及房产信息表';
因为每类数据扩展字段肯定是差异化的,而且取值与字段类型都可能不一样,如果全设置在这张主表,有些数据看到某个字段会感觉莫名其妙,而且表字段也会特别多。所以我这里的思路:差异化字段存储在扩展信息表,有业务逻辑的与前端沟通好设置的字段名定死,后期如果扩展字段用的比较多,做数据过滤频繁再考虑迁移到主表(实际就在从表也是可以过滤查询,就是会有点麻烦,一会见我后面的内容),从表表结构:
CREATE TABLE `space_reach_house_extend` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`space_reach_house_id` bigint DEFAULT NULL COMMENT '空间房源主表id',
`en_name` varchar(50) DEFAULT NULL COMMENT '字段key',
`cn_name` varchar(200) DEFAULT NULL COMMENT '字段中文名',
`val` varchar(200) DEFAULT NULL COMMENT '字段值',
PRIMARY KEY (`id`),
KEY `space_reach_house_id_idx` (`space_reach_house_id`) USING BTREE COMMENT '空间房源主记录id索引',
KEY `en_name_idx` (`en_name`) USING BTREE COMMENT '扩展属性字符串索引'
) ENGINE=InnoDB AUTO_INCREMENT=165 DEFAULT CHARSET=utf8;
从表的设计就是设置成键值对,与主表主键关联。
二、查询思路
我这里是如果是扩展字段做过滤查询,那么传参就要前端把en_name、val传参到后端,看看我的postman请求示例:
{
"spaceReachHouseInfo": {
"subjectId": 91,
"orgId": 41,
"parentId": 20,
"houseType": 1
},
"extendList": [ //扩展信息,不在主表上的数据,传enName、val,必传
{
"enName": "atFloor",
"val": "1"
},
{
"enName": "houseRoomType",
"val": "3"
},
{
"enName": "houseHallType",
"val": "2"
},
{
"enName": "houseToiletType",
"val": "1"
}
]
}
看看我的扩展表数据可能更直观,这里给一条看下:
这是主表id为43的扩展字段信息,键值对。这里特殊字段是冗余了一份xxName字段存储的,在新增、修改时同步处理,补充到前台传参的这种只有id、value值的扩展字段extendList里面进行冗余保存。
三、mybatis的过滤sql
上面可以看到将要过滤的字段、字段值都放在extendList里传过来了,我这里全部时做的=过滤。其实如果要做其他过滤,可以扩展一个filterType,例如传1:=过滤、2:like过滤,3:<过滤等等。看我的这个列表查询sql:
<select id="querySpaceHouseDtoData"
parameterType="com.fillersmart.fsihouse.data.vo.spacereachhouse.SpaceReachHouseInfoPageVo"
resultMap="SpaceReachHouseDtoSubjectParentMap">
SELECT r.*
FROM (
SELECT
<if test="extendList != null and extendList.size() != 0">
<foreach collection="extendList" item="item">
(SELECT #{item.val}
FROM space_reach_house_extend e
WHERE e.space_reach_house_id = s.id
AND e.en_name = #{item.enName}) AS #{item.enName},
</foreach>
</if>
s.*
FROM space_reach_house_info s
WHERE 1 = 1
<if test="spaceReachHouseInfo.orgId != null">
AND s.org_id = #{spaceReachHouseInfo.orgId}
</if>
<if test="spaceReachHouseInfo.subjectId != null">
AND s.subject_id = #{spaceReachHouseInfo.subjectId}
</if>
<if test="spaceReachHouseInfo.houseType != null">
AND s.house_type = #{spaceReachHouseInfo.houseType}
</if>
<if test="spaceReachHouseInfo.dataType != null">
AND s.data_type = #{spaceReachHouseInfo.dataType}
</if>
<if test="spaceReachHouseInfo.parentIds != null and spaceReachHouseInfo.parentIds != ''">
AND s.parent_ids Like CONCAT('', #{spaceReachHouseInfo.parentIds}, '%')
</if>
AND s.is_deleted = 0
<if test="spaceReachHouseInfo.principalId != null">
AND s.principal_id = #{spaceReachHouseInfo.principalId}
</if>
<if test="spaceReachHouseInfo.dataStatus != null">
AND s.data_status = #{spaceReachHouseInfo.dataStatus}
</if>
<if test="spaceReachHouseInfo.code != null and spaceReachHouseInfo.code != ''">
AND s.code LIKE CONCAT('%'
, #{spaceReachHouseInfo.code}
, '%')
</if>
) r
WHERE 1 = 1
<if test="extendList != null and extendList.size() != 0">
<foreach collection="extendList" item="item">
AND r.${item.enName} = #{item.val}
</foreach>
</if>
</select>
核心就在2个extendList的遍历处理,一个在字段处拼接子查询、一个在最外层的条件处拼接过滤条件,都是依赖传参的en_name、val。
思路大家可以好好体会,这里要说的是:
1、内层一定要将主表的一些过滤条件拼接
2、子表最好是将主表id、en_name的索引建上
以上实际是为了提高性能,其实是不建议搞这种某个字段子查询的,大厂都是说要做表连接查询,不做子查询,但是这里我也确实只想到了这个巧方法,如果后期有性能瓶颈,那肯定是某个字段不适合存储在扩展表了,那就要迁移到主表上了。
这样java代码部分还是可以使用pageHelper分页查询,就分享到这里,如果大家有好的设计思路可以告诉我一声,共同进步。
谢谢大家。