歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我我的微信「java_front」一塊兒交流學習java
1 需求背景
在系統中用戶一共有三種角色:普通用戶,管理員,超級管理員,如今須要設計一張用戶角色表記錄這類信息。咱們不難設計出以下方案:面試

咱們使用1表示是,0表示否,那麼觀察上表不可貴出,用戶一有用超級管理員角色,用戶二具備管理員角色,用戶三具備普通用戶角色,用戶四同時具備三種角色。數據庫
若是此時新增長一種角色呢?那麼新增一個字段便可:微信

2 發現問題
按照上述作法進行表設計功能上是沒有問題的,優勢是容易理解結構清晰,可是咱們想想有沒有什麼問題?筆者遇到過以下問題:架構
在複雜業務環境一份數據可能會使用在不一樣的場景,例如上述數據存儲在MySQL數據庫,這一份數據還會被用在以下場景:源碼分析
- 檢索數據須要同步一份到ES
- 業務方使用此表經過Flink計算業務指標
- 業務方訂閱此表Binlog消息進行業務處理
若是表結構發生變化,數據源之間就要從新進行對接,業務方也要進行代碼修改,這樣開發成本比較很是高。有沒有辦法避免此類問題?學習
3 解決方案
咱們能夠經過位圖法,這樣同一個字段能夠表示多個含義。首先設計以下數據表,userFlag字段先不填。this

咱們使用位圖法每個bit表示一種角色spa

咱們使用位圖法表示以下數據表設計

用戶一位圖以下十進制數值是4

用戶二位圖以下十進制數值是2

用戶三位圖以下十進制數值是1

用戶四位圖以下十進制數值是7

這時咱們能夠補齊數據表

4 位圖法詳解
本章節咱們分析位圖法方案一些關鍵節點。
4.1 枚舉定義
定義枚舉時不要直接定義爲一、二、4這類數字,而是採用位移方式定義,這樣使用者能夠明白設計者意圖。
/** * 用戶角色枚舉 * * @author JAVA前線 * */ public enum UserRoleEnum { // 1 -> 00000001 NORMAL(1, "普通用戶"), // 2 -> 00000010 MANAGER(1 << 1, "管理員"), // 4 -> 00000100 SUPER(1 << 2, "超級管理員") ; private int code; private String description; private UserRoleEnum(Integer code, String description) { this.code = code; this.description = description; } public String getDescription() { return description; } public int getCode() { return this.code; } }
4.2 維護角色
假設用戶已經具備普通用戶角色,咱們須要爲其增長管理員角色,這就是新增角色,與之對應還有刪除角色和查詢角色,這些操做須要用到爲位運算,說明詳見代碼註釋。
/** * 用戶角色枚舉 * * @author JAVA前線 * */ public enum UserRoleEnum { // 1 -> 00000001 NORMAL(1, "普通用戶"), // 2 -> 00000010 MANAGER(1 << 1, "管理員"), // 4 -> 00000100 SUPER(1 << 2, "超級管理員") ; // 新增角色 -> 位或操做 // oldRole -> 00000001 -> 普通用戶 // addRole -> 00000010 -> 新增管理員 // newRole -> 00000011 -> 普通用戶和管理員 public static Integer addRole(Integer oldRole, Integer addRole) { return oldRole | addRole; } // 刪除角色 -> 位異或操做 // oldRole -> 00000011 -> 普通用戶和管理員 // delRole -> 00000010 -> 刪除管理員 // newRole -> 00000001 -> 普通用戶 public static Integer removeRole(Integer oldRole, Integer delRole) { return oldRole ^ delRole; } // 是否有某種角色 -> 位與操做 // allRole -> 00000011 -> 普通用戶和管理員 // qryRole -> 00000001 -> 是否有管理員角色 // resRole -> 00000001 -> 有普通用戶角色 public static boolean hasRole(Integer role, Integer queryRole) { return queryRole == (role & queryRole); } private int code; private String description; private UserRoleEnum(Integer code, String description) { this.code = code; this.description = description; } public String getDescription() { return description; } public int getCode() { return this.code; } public static void main(String[] args) { System.out.println(addRole(1, 2)); System.out.println(removeRole(3, 1)); System.out.println(hasRole(3, 1)); } }
4.3 數據查詢
假設在運營後臺查詢界面中,須要查詢具備普通用戶角色的用戶數據,咱們可使用SQL語句以下
select * from user_role where (user_flag & 1) = user_flag; select * from user_role where (user_flag & b'0001') = user_flag;
使用MyBatis語句以下
<select id="selectByUserRole" resultMap="BaseResultMap" parameterType="java.util.Map"> select * from user_role where user_flag & #{userFlag} = #{userFlag} </select> <select id="selectByUserIdAndRole" resultMap="BaseResultMap" parameterType="java.util.Map"> select * from user_role where id = #{userId} and user_flag & #{userFlag} = #{userFlag} </select>
5 文章總結
本文咱們從一個簡單案例開始,分析了直接新增字段優缺點,使用新增字段方法,筆者遇到最多的問題就是在複雜業務場景中,若是新增字段數據須要新增對接工做量,增長了開發成本。
咱們介紹了位圖法,這樣一個字段就能夠表示多個含義,減小了字段冗餘,節省了對接開發成本。固然位圖法也有缺點,其增長了代碼理解成本,數據庫字段含義不直觀,須要進行轉義,你們能夠根據需求場景選擇使用。
歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我我的微信「java_front」一塊兒交流學習