前幾篇文章分享了下 MyBatis 攔截器的相關知識,這裏再分享下本身項目中遇到的一個問題,而後經過自定義的攔截器快速的解決了問題。java
SpringBoot,MyBatis.....git
最近項目中須要增長「數據權限」功能。所謂的「數據權限」是指不一樣用戶在查詢某張表的數據時看到的數據範圍是不同的,有的用戶能夠看到所有門店的數據,有的用戶只能看到部分門店的數據,這就須要給不一樣角色用戶配置不一樣的數據範圍查看權限。好比 「商品信息表」 中有所屬門店的 「門店id」 字段,用戶能看到哪些門店的商品信息數據是根據配置的數據權限來判斷的, 例如A 用戶能看到 3 家門店的商品信息數據,B 用戶能看到 4 家門店的商品信息數據。具體的實現就是在對應的 SQL 查詢語句的 WHERE 條件中加上 tableName.shopId IN (shopId1, shopId2,.....) 這個條件便可。(PS:系統中的十幾張表都有 「門店id」 字段,因此對應的就有十幾個功能須要加上數據權限)。github
具體的實現方式有如下兩種:bash
(1)在每一個須要加數據權限的地方加上對應的代碼學習
若是咱們的系統剛開始開發,對應的功能須要加數據權限,好比商品信息數據獲取咱們徹底能夠在業務代碼中這樣寫:ui
public List<GoodsInformation> searchGoodsInformation(Integer userId) {
List<Integer> shopIds = authService.getIds(userId); // 調用權限服務獲取用戶所能看到的門店
// 1. 若是 shopIds 爲空,就是能夠查詢全部門店數據
......
// 2. 若是 shopIds 不爲空,就在查詢語句中加上 tableName.shopId IN (shopId1, shopId2,.....)
......
}複製代碼
上面的 1 和 2 兩部操做,能夠放在 MyBatis 中用 if 判斷下便可,這樣能夠複用一條查詢語句。spa
(2)利用 MyBatis 的攔截器加 AOP 的方式實現插件
若是咱們的項目開發到後期了,這個時候產品說要給系統中十幾個查詢頁面加上數據權限功能,聽到這句話咱們是有點矇蔽的。若是按照方法 1,咱們須要修改業務代碼和 MyBatis 中的查詢語句,仍是比較耗費時間的。code
其實咱們分析下方法 1 中的代碼能夠發現:根據 userId 獲取門店集合是通用的代碼,由於全部的數據權限功能都是根據 shopId 來判斷的,因此這種通用的代碼能夠用 AOP 來實現,這樣就不用修改散落在不一樣包中的業務代碼了。還有給不一樣表加 tableName.shopId IN (shopId1, shopId2,.....) 這個條件,除了表名不一樣和字段名可能不一樣(有的叫shopId,有的叫shop等) 以外,IN (shopId1, shopId2,.....) 這個徹底是相同的,因此咱們能夠自定義 MyBatis 攔截器 經過修改查詢語句,添加咱們須要的條件便可實現。其中對不一樣的表名和字段名,咱們能夠用自定義的註解來配置。orm
具體實現:
將 userId 放入自定義的 threadlocal 中
@ControllerAdvice
public class ApplicationControllerAdvice {
@ModelAttribute
public void addAttributes(@RequestParam(required = false) String userId) { if(userId != null) {
UserContext userContext = new UserContext();
userContext.setUserId(userId);
// 將 userId 放入 threadlocal 中
userContextHolder.userContextThreadLocal.set(userContext);
}
}
}複製代碼
而後定義 AOP 切面,根據 userId 獲取 shopIds 集合以及方法上自定義註解中的 tableName 和 field 信息,並存入 threadlocal 中:
@Before(value = "execution(* my.study.dataauthplugin.demo.*(..)) && @annotation(dataAuthentication)") public void getDataAuth(DataAuthentication dataAuthentication) throws Throwable {
UserContext uc = UserContextHolder.userContextThreadLocal.get();
if (uc != null && !"".equals(uc.getUserId()) && dataAuthentication != null) {
// 獲取 shopId 集合
List<Integer> ids = authService.getIds(uc.getUserId());
if (ids != null && !ids.isEmpty()) {
List<String> fields = new ArrayList<>();
List<String> tableNames = new ArrayList<>();
// 獲取自定義註解中的 tableNames 和 fields
SqlSignature[] signatures = dataAuthentication.value();
if(signatures.length > 0) {
for (int i = 0; i < signatures.length; i++) {
fields.add(signatures[i].field());
tableNames.add(signatures[i].tableName());
}
}
// 將 shopIds ,fields,tableNames 存入 threadlocal 中
uc.init(ids, fields, tableNames);
}
}
}
}複製代碼
而後定義 MyBatis 插件,根據 threadlocal 中的值,修改對應的查詢語句。
具體實現見:github.com/qianhongxin…
對本身項目中用到的相關技術,咱們須要深刻去學習。好比 MyBatis 提供了插件這種擴展機制,那咱們就要充分去用好它,提升開發效率,爲業務賦能。
人說脫離業務談技術是耍牛氓!話說回來,想要更好的支持業務,不深刻的學習好業務用到的技術,拿什麼來支持業務呢?我的以爲深刻學習技術源碼是頗有必要的,雖然說暫時用不上,可是經過持續的閱讀技術源碼,也能給咱們開發業務代碼提供更好的實現方案。