利用位运算实现一个字段表示多个属性

前言

在数据库设计中,经常出现这样一种场景,如:某个系统的用户表,现阶段用户存在【是否新手】、【是否风险评测】、【是否实名认证】、【是否投资】等四个并存的属性,那么你会怎么设计表结构呢,在用户表定义四个字段?当然这样肯定是可行的,但是你设想这样一个问题,随着业务的扩展,用户可能会增加其他的属性比如【是否vip】等等属性,那岂不是还需要alter用户表结构,这种修改表结构不仅影响性能同时修改的地方很多,维护成本比较高。

这里提供一种优雅的设计,通过一个字段来维护多个并存的属性。

设计思路

数据库表

用户表不用设计五个字段表示五种属性,用一个user_propery字段来表示用户属性,数据类型采用bigint,因为bigint对应的是java中的long类型,long类型的最大值是2^63,也就是用long类型最多可以并行表示63个位状态,也就是可以同时表示63个并行的用户属性。

用户属性枚举

定义一个枚举类,如下:

package com.whfax.user.inf.constants;

public enum UserProperty {

    OPEN_ACCOUNT_FLAG(1, "1-开户成功"),

    FDD_FLAG(1 << 1, " 2-法大大签约成功"),

    RISK_FLAG(1 << 2, "4-风险测评成功"),

    NEW_FLAG(1 << 3, "8-新手");

    public long value;
    public String desc;

    UserProperty(long value, String desc) {
        this.value = value;
        this.desc = desc;
    }


    public static String getDesc(long value) {
        for (UserProperty property : UserProperty.values()) {
            if (property.value == value) {
                return property.desc;
            }
        }
        return "未知";
    }


    public static boolean isOpenAccountFlag(long flag) {
        return (flag & OPEN_ACCOUNT_FLAG.value) == OPEN_ACCOUNT_FLAG.value;
    }

    public static boolean isFddFlag(long flag) {
        return (flag & FDD_FLAG.value) == FDD_FLAG.value;
    }

    public static boolean isRiskFlag(long flag) {
        return (flag & RISK_FLAG.value) == RISK_FLAG.value;
    }


    public static boolean isNewFlag(long flag) {
        return (flag & NEW_FLAG.value) == NEW_FLAG.value;
    }

}

这种方式扩展性就很好了,后面随着业务扩展,只需要在这个枚举类中增加对应的属性就行了。

dao层操作用户属性

  • 增加用户属性操作
@Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void addUserProperty(long userId, UserProperty userProperty) {
        log.info("addUserProperty enter,userId:{}--userProperty:{}", userId, userProperty);

        String update = "update " + TableNameContants.TABLE_USER
                        + " set user_property=(user_property|?) ,update_time=now() where id=?";
        List para = new ArrayList();
        para.add(userProperty.value);
        para.add(userId);
        log.info("update:{}--para:{}", update, para.toArray());
        mainDao.update(update, para.toArray());
    } 
  
  • 剔除用户属性
@Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void removeUserProperty(long userId, UserProperty userProperty) {
        log.info("removeUserProperty enter,userId:{}--userProperty:{}", userId, userProperty);
        List para = new ArrayList();
        String update = "update " + TableNameContants.TABLE_USER
                        + " set user_property=(user_property&(~?)) ,update_time=now() where id=?";
        para.add(userProperty.value);
        para.add(userId);
        log.info("update:{}--para:{}", update, para.toArray());
        mainDao.update(update, para.toArray());

    } 
  

说明

  • 可能有的小伙伴就问了,为啥每种属性都要定义成2的n次幂,这是因为每一个位上都是不同的,表示的结果是唯一的,二进制【|】运算就相当于十进制的加法,m|n<=(m+n),等式成立的条件就是m和n都是2的整数次方;
  • (~m)&n,可以剔除n中包含m的位;
  • 判断n中是否包含m可以使用,m&n的值和m做判断,若相等则包含,否则则不包含。
  • 上面的设计有诸与易扩展性等优点,但是也有一定的缺陷。其一就是针对两个互斥的属性,不太适用;其二是,可读性较差,比如user_property中存储的是6,但是枚举值并没有6,还需要做额外的判断,才能知晓这条数据包含哪些属性。

你可能感兴趣的:(数据库设计,java,二进制应用,位运算,一个字段表示多个属性,数据库设计)