今日視點(diǎn):位圖法在mongodb中的應(yīng)用
JAVA前線
歡迎大家關(guān)注公眾號(hào)「JAVA前線」查看更多精彩分享,主要內(nèi)容包括源碼分析、實(shí)際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時(shí)也非常歡迎大家加我微信「java_front」一起交流學(xué)習(xí)
【資料圖】
假設(shè)系統(tǒng)用戶一共有三種角色:普通用戶、管理員、超級(jí)管理員,現(xiàn)在需要設(shè)計(jì)一張用戶角色表記錄這類信息。我們不難設(shè)計(jì)出如下方案:
id | name | super | admin | normal |
---|---|---|---|---|
101 | 用戶一 | 1 | 0 | 0 |
102 | 用戶二 | 0 | 1 | 0 |
103 | 用戶三 | 0 | 0 | 1 |
104 | 用戶四 | 1 | 1 | 1 |
用戶一具有超級(jí)管理員角色,用戶二具有管理員角色,用戶三具有普通用戶角色,用戶四同時(shí)具有三種角色。
2 發(fā)現(xiàn)問題如果新增加一種角色呢?可以新增一個(gè)字段:
id | name | super | admin | normal | new |
---|---|---|---|---|---|
101 | 用戶一 | 1 | 0 | 0 | 0 |
102 | 用戶二 | 0 | 1 | 0 | 0 |
103 | 用戶三 | 0 | 0 | 1 | 0 |
104 | 用戶四 | 1 | 1 | 1 | 0 |
按照上述一個(gè)字段表示一種角色設(shè)計(jì)表,功能沒有問題,優(yōu)點(diǎn)是容易理解結(jié)構(gòu)清晰,但是我們想一想有沒有什么問題?筆者遇到過如下問題:
在復(fù)雜業(yè)務(wù)環(huán)境一份數(shù)據(jù)可能會(huì)使用在不同場景,例如上述數(shù)據(jù)存儲(chǔ)在MySQL數(shù)據(jù)庫,這一份數(shù)據(jù)還會(huì)被用在如下場景:
檢索數(shù)據(jù)需要同步一份到ES使用此表通過Flink計(jì)算業(yè)務(wù)指標(biāo)訂閱此表Binlog消息進(jìn)行業(yè)務(wù)處理如果表結(jié)構(gòu)發(fā)生變化,數(shù)據(jù)源之間需要重新對(duì)接,業(yè)務(wù)方也要進(jìn)行代碼修改,這樣開發(fā)成本非常高。有沒有辦法避免此類問題?
3 解決方案我們可以使用位圖法,同一個(gè)字段可以表示多個(gè)業(yè)務(wù)含義。首先設(shè)計(jì)如下數(shù)據(jù)表,userFlag字段暫時(shí)不填:
id | name | user_flag |
---|---|---|
101 | 用戶一 | 暫時(shí)不填 |
102 | 用戶二 | 暫時(shí)不填 |
103 | 用戶三 | 暫時(shí)不填 |
104 | 用戶四 | 暫時(shí)不填 |
位圖每一個(gè)bit表示一種角色:
使用位圖法表示如下數(shù)據(jù):
id | name | super | admin | normal |
---|---|---|---|---|
101 | 用戶一 | 1 | 0 | 0 |
102 | 用戶二 | 0 | 1 | 0 |
103 | 用戶三 | 0 | 0 | 1 |
104 | 用戶四 | 1 | 1 | 1 |
用戶一位圖如下,十進(jìn)制數(shù)值等于4:
用戶二位圖如下,十進(jìn)制數(shù)值等于2:
用戶三位圖如下,十進(jìn)制數(shù)值等于1:
用戶四位圖如下,十進(jìn)制數(shù)值等于7:
現(xiàn)在可以填寫數(shù)據(jù)表第三列:
id | name | user_flag |
---|---|---|
101 | 用戶一 | 4 |
102 | 用戶二 | 2 |
103 | 用戶三 | 1 |
104 | 用戶四 | 7 |
本文結(jié)合mongodb實(shí)現(xiàn)思路有兩種:
方案一:取出二進(jìn)制字段在應(yīng)用層運(yùn)算方案二:在數(shù)據(jù)層直接運(yùn)算二進(jìn)制字段4.1 用戶實(shí)體用戶實(shí)體對(duì)應(yīng)數(shù)據(jù)表user:
@Document(collection="user")publicclassUser{@Id@Field("_id")privateStringid;@Field("userId")privateStringuserId;@Field("role")privateLongrole;}4.2 用戶角色
定義枚舉時(shí)不要直接定義為1、2、4這類數(shù)字,應(yīng)該采用位移方式進(jìn)行定義,這樣使用者可以明白設(shè)計(jì)者的意圖。
publicenumUserRoleEnum{//1->00000001NORMAL(1L<<0,"普通用戶"),//2->00000010MANAGER(1L<<1,"管理員"),//4->00000100SUPER(1L<<2,"超級(jí)管理員"),;privateLongcode;privateStringdescription;privateUserRoleEnum(Longcode,Stringdescription){this.code=code;this.description=description;}//新增角色->位或操作//oldRole->00000001->普通用戶角色//addRole->00000010->新增管理員角色//newRole->00000011->具有普通用戶和管理員角色publicstaticLongaddRole(LongoldRole,LongaddRole){returnoldRole|addRole;}//刪除角色->異或操作//oldRole->00000011->普通用戶和管理員角色//delRole->00000010->刪除管理員角色//newRole->00000001->普通用戶角色publicstaticLongremoveRole(LongoldRole,LongdelRole){returnoldRole^delRole;}//是否具有某種角色->位與操作//allRole->00000011->普通用戶和管理員角色//qryRole->00000001->查詢是否具有管理員角色//resRole->00000001->具有管理員角色publicstaticbooleanhasRole(Longrole,LongqueryRole){LongresRole=(role&queryRole);returnqueryRole==resRole;}}4.3 數(shù)據(jù)準(zhǔn)備
新增用戶一到用戶四:
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 應(yīng)用層運(yùn)算
應(yīng)用層運(yùn)算有三個(gè)關(guān)鍵步驟:
查詢用戶角色內(nèi)存計(jì)算新角色更新數(shù)據(jù)庫@ServicepublicclassUserBizService{@ResourceprivateMongoTemplatemongoTemplate;//查詢用戶publicUsergetUser(StringuserId){Queryquery=newQuery();Criteriacriteria=Criteria.where("userId").is(userId);query.addCriteria(criteria);Useruser=mongoTemplate.findOne(query,User.class);returnuser;}//新增角色publicbooleanaddRole(StringuserId,LongaddRole){//查詢用戶角色Useruser=getUser(userId);//計(jì)算新角色LongfinalRole=UserRoleEnum.addRole(user.getRole(),addRole);//更新數(shù)據(jù)庫Queryquery=newQuery();Criteriacriteria=Criteria.where("userId").is(userId);query.addCriteria(criteria);Updateupdate=newUpdate();update.set("role",finalRole);mongoTemplate.updateFirst(query,update,User.class);returntrue;}//刪除角色publicbooleanremoveRole(StringuserId,LongdelRole){//查詢用戶角色Useruser=getUser(userId);//計(jì)算新角色LongfinalRole=UserRoleEnum.removeRole(user.getRole(),delRole);//更新數(shù)據(jù)庫Queryquery=newQuery();Criteriacriteria=Criteria.where("userId").is(userId);query.addCriteria(criteria);Updateupdate=newUpdate();update.set("role",finalRole);mongoTemplate.updateFirst(query,update,User.class);returntrue;}//查詢用戶是否具有某種角色publicbooleanqueryRole(StringuserId,LongqueryRole){//查詢用戶角色Useruser=getUser(userId);//計(jì)算是否具有某種角色returnUserRoleEnum.hasRole(user.getRole(),queryRole);}}4.5 數(shù)據(jù)層運(yùn)算4.5.1 位運(yùn)算操作符(1) 查詢操作符
操作符 | 含義 |
---|---|
bitsAllClear | 指定二進(jìn)制位全為0 |
bitsAllSet | 指定二進(jìn)制位全為1 |
bitsAnyClear | 任意指定二進(jìn)制位為0 |
bitsAnySet | 任意指定二進(jìn)制位為1 |
下列語句可以查出用戶四:
0-2三個(gè)位置全部等于1db.user.find({"role":{$bitsAllSet:[0,1,2]}})0-2任意一個(gè)位置等于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) 計(jì)算操作符
操作符 | 含義 | 操作 |
---|---|---|
and | 位與 | 查詢角色 |
or | 位或 | 新增角色 |
xor | 位異或 | 刪除角色 |
db.user.update({"userId":"user3"},{$bit:{"role":{or:NumberLong(4)}}})user4刪除普通用戶角色
db.user.update({"userId":"user4"},{$bit:{"role":{xor:NumberLong(1)}}})4.5.2 代碼實(shí)例
@ServicepublicclassUserBizService{/**新增角色*/publicbooleanaddRoleBit(StringuserId,LongaddRole){Queryquery=newQuery();Criteriacriteria=Criteria.where("userId").is(userId);query.addCriteria(criteria);Updateupdate=newUpdate();update.bitwise("role").or(addRole);mongoTemplate.updateFirst(query,update,User.class);returntrue;}/***刪除角色*/publicbooleanremoveRoleBit(StringuserId,LongaddRole){Queryquery=newQuery();Criteriacriteria=Criteria.where("userId").is(userId);query.addCriteria(criteria);Updateupdate=newUpdate();update.bitwise("role").xor(addRole);mongoTemplate.updateFirst(query,update,User.class);returntrue;}/***查詢r(jià)olePosition位置全部等于0的用戶**表示不具有rolePositions中所有角色的用戶*/publicList5 文章總結(jié)queryRoleAllClear(List rolePositions){Criteriacriteria=Criteria.where("role").bits().allClear(rolePositions);List users=mongoTemplate.query(User.class).matching(criteria).all();returnusers;}/***查詢r(jià)olePosition位置任一等于0的用戶**表示不具有rolePositions中任一角色的用戶*/publicList queryRoleAnyClear(List rolePositions){Criteriacriteria=Criteria.where("role").bits().anyClear(rolePositions);List users=mongoTemplate.query(User.class).matching(criteria).all();returnusers;}/***查詢r(jià)olePosition位置全部等于1的用戶**表示具有rolePositions中所有角色的用戶*/publicList queryRoleAllSet(List rolePositions){Criteriacriteria=Criteria.where("role").bits().allSet(rolePositions);List users=mongoTemplate.query(User.class).matching(criteria).all();returnusers;}/***查詢r(jià)olePosition位置任一等于1的用戶**表示具有rolePositions中任一角色的用戶*/publicList queryRoleAnySet(List rolePositions){Criteriacriteria=Criteria.where("role").bits().anySet(rolePositions);List users=mongoTemplate.query(User.class).matching(criteria).all();returnusers;}}
本文我們從一個(gè)簡單案例開始,分析了直接新增字段的優(yōu)缺點(diǎn)。新增字段方案遇到最多問題就是在復(fù)雜業(yè)務(wù)場景中,需要新增數(shù)據(jù)對(duì)接工作量,增加了開發(fā)維護(hù)成本。
我們又介紹了位圖法,一個(gè)字段就可以表示多種業(yè)務(wù)含義,減少了字段冗余,節(jié)省了對(duì)接開發(fā)成本。同時(shí)位圖法增加了代碼理解成本,數(shù)據(jù)庫字段含義不直觀,需要進(jìn)行轉(zhuǎn)義,大家可以根據(jù)業(yè)務(wù)需求場景選擇。
JAVA前線
歡迎大家關(guān)注公眾號(hào)「JAVA前線」查看更多精彩分享,主要內(nèi)容包括源碼分析、實(shí)際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時(shí)也非常歡迎大家加我微信「java_front」一起交流學(xué)習(xí)
相關(guān)閱讀
-
世界熱推薦:今晚7:00直播丨下一個(gè)突破...
今晚19:00,Cocos視頻號(hào)直播馬上點(diǎn)擊【預(yù)約】啦↓↓↓在運(yùn)營了三年... -
NFT周刊|Magic Eden宣布支持Polygon網(wǎng)...
Block-986在NFT這樣的市場,每周都會(huì)有相當(dāng)多項(xiàng)目起起伏伏。在過去... -
環(huán)球今亮點(diǎn)!頭條觀察 | DeFi的興衰與...
在比特幣得到機(jī)構(gòu)關(guān)注之后,許多財(cái)務(wù)專家預(yù)測世界將因?yàn)榧用茇泿诺?.. -
重新審視合作,體育Crypto的可靠關(guān)系才能雙贏
Block-987即使在體育Crypto領(lǐng)域,人們的目光仍然集中在FTX上。隨著... -
簡訊:前端單元測試,更進(jìn)一步
前端測試@2022如果從2014年Jest的第一個(gè)版本發(fā)布開始計(jì)算,前端開發(fā)... -
焦點(diǎn)熱訊:劉強(qiáng)東這波操作秀
近日,劉強(qiáng)東發(fā)布京東全員信,信中提到:自2023年1月1日起,逐步為...