欢迎咱们重视大众号「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表明一种人物:
运用位图法表明如下数据表:
id | name | super | admin | normal |
---|---|---|---|---|
101 | 用户一 | 1 | 0 | 0 |
102 | 用户二 | 0 | 1 | 0 |
103 | 用户三 | 0 | 0 | 1 |
104 | 用户四 | 1 | 1 | 1 |
用户一位图如下,十进制数值是4
用户二位图如下,十进制数值是2
用户三位图如下,十进制数值是1
用户四位图如下,十进制数值是7
现在能够填写数据表第三列:
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 |
本章节以用户四为例,下列语句均能够查出用户四:
- 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」一同交流学习