欢迎咱们重视大众号「JAVA前线」检查更多精彩共享文章,主要包括源码剖析、实践应用、架构思维、职场共享、产品考虑等等,一同欢迎咱们加我微信「java_front」一同交流学习


1 需求布景

假定系统用户一共有三种人物:普通用户、管理员、超级管理员,现在需求规划一张用户人物表记载这类信息。咱们不难规划出如下计划:

id name super admin normal
101 用户一 1 0 0
102 用户二 0 1 0
103 用户三 0 0 1
104 用户四 1 1 1

用户一具有超级管理员人物,用户二具有管理员人物,用户三具有普通用户人物,用户四一同具有三种人物。


2 发现问题

如果此时新增加一种人物呢?能够新增一个字段:

id name super admin normal new_role
101 用户一 1 0 0 0
102 用户二 0 1 0 0
103 用户三 0 0 1 0
104 用户四 1 1 1 0

按照上述一个字段表明一种人物规划表,功用没有问题,长处是简略了解结构明晰,但是咱们想一想有没有什么问题?笔者遇到过如下问题:

在杂乱事务环境一份数据或许会运用在不同场景,例如上述数据存储在MySQL数据库,这一份数据还会被用在如下场景:

  • 检索数据需求同步一份到ES
  • 运用此表经过Flink核算事务指标
  • 订阅此表Binlog消息进行事务处理

如果表结构发生变化,数据源之间就要从头对接,事务方也要进行代码修正,这样开发本钱非常高。有没有方法防止此类问题?


3 解决计划

咱们能够运用位图法,同一个字段能够表明多个事务意义。首要规划如下数据表,userFlag字段暂时不填:

id name user_flag
101 用户一 暂时不填
102 用户二 暂时不填
103 用户三 暂时不填
104 用户四 暂时不填

规划位图每一个bit表明一种人物:


位图法在mongodb中的应用


运用位图法表明如下数据表:


id name super admin normal
101 用户一 1 0 0
102 用户二 0 1 0
103 用户三 0 0 1
104 用户四 1 1 1

用户一位图如下,十进制数值是4


位图法在mongodb中的应用


用户二位图如下,十进制数值是2


位图法在mongodb中的应用


用户三位图如下,十进制数值是1


位图法在mongodb中的应用


用户四位图如下,十进制数值是7


位图法在mongodb中的应用


现在能够填写数据表第三列:


id name user_flag
101 用户一 4
102 用户二 2
103 用户三 1
104 用户四 7


4 代码实例

本文结合mongodb进行实例演示,实现思路有两种:

  • 取出二进制字段在应用层运算
  • 在数据层直接运算二进制字段

4.1 用户实体

用户实体对应数据表user

@Document(collection = "user")
public class User {
	@Id
	@Field("_id")
	private String id;
	@Field("userId")
	private String userId;
	@Field("role")
	private Long role;
}

4.2 用户人物

界说枚举时不要直接界说为1、2、4这类数字,应该采用位移方法进行界说,这样运用者能够了解规划者的目的。

public enum UserRoleEnum {
    // 1 -> 00000001
    NORMAL(1L << 0, "普通用户"),
    // 2 -> 00000010
    MANAGER(1L << 1, "管理员"),
    // 4 -> 00000100
    SUPER(1L << 2, "超级管理员"),
    ;
    private Long code;
    private String description;
    private UserRoleEnum(Long code, String description) {
        this.code = code;
        this.description = description;
    }
    // 新增人物 -> 位或操作
    // oldRole -> 00000001 -> 普通用户人物
    // addRole -> 00000010 -> 新增管理员人物
    // newRole -> 00000011 -> 具有普通用户和管理员人物
    public static Long addRole(Long oldRole, Long addRole) {
        return oldRole | addRole;
    }
    // 删去人物 -> 异或操作
    // oldRole -> 00000011 -> 普通用户和管理员人物
    // delRole -> 00000010 -> 删去管理员人物
    // newRole -> 00000001 -> 普通用户人物
    public static Long removeRole(Long oldRole, Long delRole) {
        return oldRole ^ delRole;
    }
    // 是否具有某种人物 -> 位与操作
    // allRole -> 00000011 -> 普通用户和管理员人物
    // qryRole -> 00000001 -> 查询是否具有管理员人物
    // resRole -> 00000001 -> 具有管理员人物
    public static boolean hasRole(Long role, Long queryRole) {
        Long resRole = (role & queryRole);
        return queryRole == resRole;
    }
}

4.3 数据预备

新增用户一到用户四数据:

db.user.insertMany([
    {
        "userId": "user1",
        "role": NumberLong(4)
    },
    {
        "userId": "user2",
        "role": NumberLong(2)
    },
    {
        "userId": "user3",
        "role": NumberLong(1)
    },
    {
        "userId": "user4",
        "role": NumberLong(7)
    }
])

4.4 应用层运算

应用层运算有三个关键步骤:

  • 数据库查询用户人物
  • 内存中核算新人物
  • 更新数据库

@Service
public class UserBizService {
    @Resource
    private MongoTemplate mongoTemplate;
    // 查询用户
    public User getUser(String userId) {
        Query query = new Query();
        Criteria criteria = Criteria.where("userId").is(userId);
        query.addCriteria(criteria);
        User user = mongoTemplate.findOne(query, User.class);
        return user;
    }
    // 新增人物
    public boolean addRole(String userId, Long addRole) {
        // 查询用户人物
        User user = getUser(userId);
        // 核算新人物
        Long finalRole = UserRoleEnum.addRole(user.getRole(), addRole);
        // 更新数据库
        Query query = new Query();
        Criteria criteria = Criteria.where("userId").is(userId);
        query.addCriteria(criteria);
        Update update = new Update();
        update.set("role", finalRole);
        mongoTemplate.updateFirst(query, update, User.class);
        return true;
    }
    // 删去人物
    public boolean removeRole(String userId, Long delRole) {
        // 查询用户人物
        User user = getUser(userId);
        // 核算新人物
        Long finalRole = UserRoleEnum.removeRole(user.getRole(), delRole);
        // 更新数据库
        Query query = new Query();
        Criteria criteria = Criteria.where("userId").is(userId);
        query.addCriteria(criteria);
        Update update = new Update();
        update.set("role", finalRole);
        mongoTemplate.updateFirst(query, update, User.class);
        return true;
    }
    // 查询用户是否具有某种人物
    public boolean queryRole(String userId, Long queryRole) {
        // 查询用户人物
        User user = getUser(userId);
        // 核算是否具有某种人物
        return UserRoleEnum.hasRole(user.getRole(), queryRole);
    }
}

4.5 数据层运算

4.5.1 位运算操作符

mongodb在3.2版本之后,供给查询操作符和核算操作符:


(1) 查询操作符

操作符 意义
$bitsAllClear 参数指定二进制位数悉数等于0
$bitsAllSet 参数指定二进制位数悉数等于1
$bitsAnyClear 恣意一位参数指定二进制位数为0
$bitsAnySet 恣意一位参数指定二进制位数为1

本章节以用户四为例,下列语句均能够查出用户四:


位图法在mongodb中的应用


  • 0-2三个方位悉数等于1
db.user.find({
    "role": {
        $bitsAllSet: [0, 1, 2]
    }
})

  • 0-2恣意一个方位等于1
db.user.find({
    "role": {
        $bitsAnySet: [0, 1, 2]
    }
})

  • 3-7方位悉数等于0
db.user.find({
    "role": {
        $bitsAllClear: [3, 4, 5, 6, 7]
    }
})

  • 3-7方位恣意等于0
db.user.find({
    "role": {
        $bitsAnyClear: [3, 4, 5, 6, 7]
    }
})

(2) 核算操作符


操作符 意义 操作
and 位与 查询人物
or 位或 新增人物
xor 位异或 删去人物

  • user3新增超级管理员人物
db.user.update({
    "userId": "user3"
}, {
    $bit: {
        "role": {
            or: NumberLong(4)
        }
    }
})

  • user4删去普通用户人物
db.user.update({
    "userId": "user4"
}, {
    $bit: {
        "role": {
            xor: NumberLong(1)
        }
    }
})

4.5.2 代码实例

@Service
public class UserBizService {
    /*
     * 新增人物
     */
    public boolean addRoleBit(String userId, Long addRole) {
        Query query = new Query();
        Criteria criteria = Criteria.where("userId").is(userId);
        query.addCriteria(criteria);
        Update update = new Update();
        update.bitwise("role").or(addRole);
        mongoTemplate.updateFirst(query, update, User.class);
        return true;
    }
    /**
     * 删去人物
     */
    public boolean removeRoleBit(String userId, Long addRole) {
        Query query = new Query();
        Criteria criteria = Criteria.where("userId").is(userId);
        query.addCriteria(criteria);
        Update update = new Update();
        update.bitwise("role").xor(addRole);
        mongoTemplate.updateFirst(query, update, User.class);
        return true;
    }
    /**
     * 查询rolePosition方位悉数等于0的用户
     *
     * 表明不具有rolePositions中所有人物的用户
     */
    public List<User> queryRoleAllClear(List<Integer> rolePositions) {
        Criteria criteria = Criteria.where("role").bits().allClear(rolePositions);
        List<User> users = mongoTemplate.query(User.class).matching(criteria).all();
        return users;
    }
    /**
     * 查询rolePosition方位任一等于0的用户
     *
     * 表明不具有rolePositions中任一人物的用户
     */
    public List<User> queryRoleAnyClear(List<Integer> rolePositions) {
        Criteria criteria = Criteria.where("role").bits().anyClear(rolePositions);
        List<User> users = mongoTemplate.query(User.class).matching(criteria).all();
        return users;
    }
    /**
     * 查询rolePosition方位悉数等于1的用户
     *
     * 表明具有rolePositions中所有人物的用户
     */
    public List<User> queryRoleAllSet(List<Integer> rolePositions) {
        Criteria criteria = Criteria.where("role").bits().allSet(rolePositions);
        List<User> users = mongoTemplate.query(User.class).matching(criteria).all();
        return users;
    }
    /**
     * 查询rolePosition方位任一等于1的用户
     *
     * 表明具有rolePositions中任一人物的用户
     */
    public List<User> queryRoleAnySet(List<Integer> rolePositions) {
        Criteria criteria = Criteria.where("role").bits().anySet(rolePositions);
        List<User> users = mongoTemplate.query(User.class).matching(criteria).all();
        return users;
    }
}

5 文章总结

本文咱们从一个简略案例开端,剖析了直接新增字段的优缺点。新增字段计划遇到最多问题就是在杂乱事务场景中,需求新增数据对接工作量,增加了开发保护本钱。

咱们又介绍了位图法,一个字段就能够表明多种事务意义,减少了字段冗余,节省了对接开发本钱。一同位图法增加了代码了解本钱,数据库字段意义不直观,需求进行转义,咱们能够依据需求场景挑选。


欢迎咱们重视大众号「JAVA前线」检查更多精彩共享文章,主要包括源码剖析、实践应用、架构思维、职场共享、产品考虑等等,一同欢迎咱们加我微信「java_front」一同交流学习