多对多关系的另一种设计思考

最近在做一个功能套餐的需求,这个需求很简单,但是表结构设计方面还需好好斟酌,我们来看看这个需求!

需求描述

看下面的原型图,这是一个功能套餐管理,增值服务那一列是具体的功能,后面配的就是对应的套餐,然后套餐包含了哪些功能,就在对应的下面打钩,套餐还有其他的一些字段,总价、套餐名称、活动开始结束时间…

需求原型需求原型
表结构设计

首先增值服务这一列是固定的,可以用枚举设计,然后后面是套餐,我们直接设计一个套餐表,表中有基本的总价、套餐名称、活动开始结束时间等等字段,还有最重要的套餐中包含的增值服务字段,这个直接用一个字符串来存储,串中的值是增值服务的枚举值,多个功能的话用逗号隔开,比如“,1,2,”表示该套餐包含枚举值是1和2的两个功能。

思考

上面的表结构设计是否可行呢,其实如果没有服务单价那一列的话,是完全可行的,回到上面的原型图中,服务单价那一列表示的是某一项增值服务功能对应的单价,那个可以随时变更,也就是说我们单纯用枚举表示这几个功能是不可行了,它需要更新价格入库,那是不是也要设计一张表去存储这几个功能和相应的价格呢,好像必须这么做,一张功能表,一张套餐表,因为套餐下面有多个功能,某一种功能又可能在多个套餐表中,也就是说还需要一个去维护套餐中有多个功能的关系表,是的,还需要一张中间表!思考一下,这种设计是否合理呢?

另一种多对多关系设计

其实,上面这种方式描述多对多的关系很传统,也没有错,但是下面这种方案对于这个需求来说更为合理:

这个方案和原来上面一开始提到的“表结构设计”的方案基本一样,只不过,多了个价格而已,也就是这些功能还是用枚举的形式表示,但是功能价格还需要用一张表维护,这张表专门是为这个枚举维护价格用的,不参与其他表的关联

咱们最终的设计方案:

一个套餐表,一个功能枚举类,一个功能表(维护价格),套餐中有个串表示该套餐包含的功能。这种设计方案相比上面三个表的关联设计简洁了很多,他将套餐功能关系直接限定在了一个表中展现,当然,并不是所有的多对多关系都是可以这么设计的,这里能够这么处理,是因为功能本身就是个枚举,做成功能表只是单单维护一个价格而已,完全没必要把功能当做一个表来关联。

什么情况下这种设计合理?
  1. 我们的功能表是有限数据的,这对于套餐那边关联功能的串不会复杂,查询功能表的超时风险基本没有
  2. 这里套餐和功能是多对多的关系,但是对于功能来说,它并不需要关心功能所属的套餐有哪些
  3. 对这个业务需求来说,这样的设计,操作起来是非常简单的
总结

总之,没有不可以的设计,只有不合适的设计,至少这里来说,这种设计方案是非常可行的!

知识延伸

上面我们提到,套餐表记录所拥有的功能是用一个串来记录的,比如“,1,2,”表示该套餐包含枚举值是1和2的两个功能,也就是说要查出这两个功能对应的服务单价还要查一遍功能表,其实查询数据库总是“很花时间的”,所以我们不妨把所有功能对应的价格都加载到内存中,每次直接去内存查询价格就可以了,为什么能够这么处理呢?

  • 一方面,功能数量是可控的,而且很少,直接加载到内存中所占内存不大
  • 另一方面,功能的价格不是经常变动的,查询价格操作远远比更新价格操作多得多,所以保证查询的速度是很有必要的

来具体看看如何操作,其实第一次加载价格可以直接在容器启动的时候就把数据加载到内存:

    /**
     * 用于存储增值服务功能价格关系
     */
    private static Map<AppreciationFeature, BigDecimal> appreciationFeatureMap;

    /**
     * 套餐价格在容器启动的时候就查询出来
     */
    @PostConstruct
    private void init() {
        List<AppreciationFeatureEntity> appreciationFeatureList =
                transactionTemplate.execute(status -> appreciationFeatureRepository.findAll());

        //初始化
        appreciationFeatureMap = new HashMap<>(appreciationFeatureList.size() + 1);

        for (AppreciationFeatureEntity entity : appreciationFeatureList) {
            appreciationFeatureMap.put(entity.getAppreciationFeature(), entity.getPrice());
        }
    }

这段代码,第一行appreciationFeatureMap 就是用来存储功能和价格关系的,用map存储便于获取,key是功能,value就是对应的价格,
获取价格的话,直接从appreciationFeatureMap中获取不再需要查询数据库。

这里涉及到一个注解 @PostConstruct

  • 被该注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
  • 该注解的方法将会在依赖注入完成后被自动调用

对了,要记住的是,这个方法要在价格更新之后调用,保证内存中的价格是最新的!

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发飙的蜗牛咻咻咻~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值