在 flying 中實現或邏輯的思考

關於或邏輯的思考  java

     本篇文章咱們來探討如何使用 flying 的方式來描述帶有 」or」 關鍵字的 sql 語句(若是您對 flying 還不瞭解,請參見 https://www.oschina.net/p/flying)。一直以來,flying力求作到的就是,把每一次與數據庫交互都變爲對象交互,而不是字符串交互,由於對象相比字符串至少有如下好處:git

  • 對象是徹底解析的。好比我有一個子容器向父容器發送查詢請求,而後按照業務,父容器要在這個查詢請求上修改一個條件再追加一個條件。使用查詢對象能夠輕鬆作到這一點,由於它能被徹底解析;但解析字符串就很麻煩了(試想一下拆分一個充滿了 and 和 or 的 sql),估計只有數據庫廠商才能提供可靠的工具。
  • 不一樣數據庫的 sql 語法有區別,但它們的查詢對象相同。sql

  • 對象能夠跨語言,能夠以 json 方式傳輸保存。數據庫

 爲了作到以上這些點,做爲條件的查詢對象必須具備如下特色:json

  1. 各條件變量的賦值順序無關。
  2. 易於理解和修改。
  3. 各條件變量是「且」的關係。

       第一個特性很好理解,例如 personCondition.setNameLike(「張」); 和 personCondition.setAge(30); 就是順序無關的,flying查詢對象目前全部的條件賦值語句(包括判斷條件、分頁條件、排序條件)都是順序無關的。 數組

       第二個特性是,用戶一眼看到某個變量賦值語句就知道它的做用是什麼,並能夠按須要進行修改。mybatis

       第三個特性是,全部的條件變量其實都是用與邏輯「and」相連的。併發

       看到這裏,你們會發現,其實 flying 查詢對象只解決了一半的問題,由於對於或操做 「or」,之前根本就沒有說起,而沒說起的緣由是,在知足以上三點的基礎上解決或邏輯比較困難。而本文則嘗試解決這一問題。app

       首先,咱們拋出一個足夠複雜的sql語句:工具

select person.id, person.name, person.age, person.level from person 
    where (person.name like ‘張%’ and person.age = 25) 
    or (person.age = 27 and person.level = ‘B’) 
    or (person.name like ‘李%’ and person.level = ‘A’)

       這個複雜的sql語句如何用一個查詢對象表示呢?這裏咱們須要使用一些數學工具,首先咱們用邏輯變量來代替條件表達式:

A = "person.name like '張%'"

B = "person.age = '25'"

C = "person.age = '27'"

D = "person.level = 'B'"

E = "person.name like '李%'"

F = "person.level = 'A'"

       這樣一來以上這個邏輯表達式就簡化爲:(A∩B)∪( C∩D)∪( E∩F)

       但是這樣沒法解決問題,由於 flying 擅長解決的是以「且」關係鏈接的條件,例如 X∩Y∩Z 這樣,而以上表達式明顯不是這樣。

       可是布爾邏輯運算具備如下性質:交換律、結合律與分配律。

交換律:A∩B = B∩A

同理 A∪B = B∪A

結合律:A∩(B∩C) = A∩B∩C

同理 A∪(B∪C)= A∪B∪C

分配律:(A∩B)∪C = (A∪C)∩(B∪C)

同理(A∪B)∩C = (A∩C)∪(B∩C)

       有了這三個定律以後,咱們就能夠把(A∩B)∪( C∩D)∪( E∩F)變形爲一連串布爾變量以「∩」相連的形式:

   (A∩B)∪(C∩D)∪( E∩F)

= (((A∩B)∪C)∩((A∩B)∪D)))∪( E∩F)

= (((A∪C)∩(B∪C))∩((A∪D)∩(B∪D)))∪( E∩F)

= ((A∪C)∩(B∪C)∩(A∪D)∩(B∪D))∪(E∩F)

= (((A∪C)∩(B∪C)∩(A∪D)∩(B∪D))∪E)∩(((A∪C)∩(B∪C)∩(A∪D)∩(B∪D))∪F)

= (A∪C∪E)∩(B∪C∪E)∩(A∪D∪E)∩(B∪D∪E)∩(A∪C∪F)∩(B∪C∪F)∩(A∪D∪F)∩(B∪D∪F)

       最後的這個形式看起來是咱們用 flying 能描述的了的。實際上,對於布爾運算式有如下定理:

任何一個布爾表達式都能被轉換爲一個等價的合取範式(CNF),合取範式格式爲:C1∩C2∩……Cn;其中,Ck(1<=k<=n)稱爲合取項,每一個合取項是不包含∩的表達式。 

       這個歸併是關係型數據庫本身也會作的,由於它具備如下好處: 

  1. 合取項只要有一個爲假,整個表達式就爲假,故代碼中能夠在發現一個合取項爲假時,即中止其餘合取項的判斷,加快判斷速度,如:WHERE(0 > 1 AND s1 = 5)
  2. 由於AND操做符是可交換的,因此優化器能夠按照先易後難的順序計算表達式,一旦發現一個合取項爲假時,即中止其餘合取項的判斷,加快判斷速度。
  3. 若是一個合取項上存在索引,則先判斷索引是否可用,如能利用索引快速得出合取項的值,則能加快判斷速度。如:WHERE (A.a> 100 AND A.b = 5 AND... )

    狀況1:A表的a列上存在索引,b列無索引,則利用a上的索引找出元組,「A.b = 5」 做爲過濾條件使用;狀況2:A表的a列上不存在索引,b列有索引,則利用b上的索引找出元組,「A.a> 100」 做爲過濾條件使用。

       因此,相對於(A∩B)∪( C∩D)∪( E∩F),咱們將(A∪C∪E)∩(B∪C∪E)∩(A∪D∪E)∩(B∪D∪E)∩(A∪C∪F)∩(B∪C∪F)∩(A∪D∪F)∩(B∪D∪F)傳給數據庫,並不會增長它的查詢時間,由於它本來也須要歸併。 

       那麼接下來的問題就變成,咱們如何用代碼描述(A∪C∪E),或者更具體地說,如何用代碼描述:"person.name like '張%' or person.age = 27 or person.name like '李%'" 這樣一個查詢條件,這個解決了其它查詢條件同理也就都解決了。

       在這裏,flying新增了Or標籤類(見https://gitee.com/limeng32/mybatis.flying/blob/master/src/main/java/indi/mybatis/flying/annotations/Or.java),這個標籤的內容是ConditionMapperAnnotation標籤的數組,因此在查詢條件類中能夠有以下標籤代碼:

@Or({
  @ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike),
  @ConditionMapperAnnotation(dbFieldName = "age", conditionType = ConditionType.Equal), 
  @ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike) 
})

       同時爲了賦值方便,咱們強烈建議採用不定參數的Object[]做爲變量,因而整個代碼變成了:

@Or({
  @ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike),
  @ConditionMapperAnnotation(dbFieldName = "age", conditionType = ConditionType.Equal), 
  @ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike) 
})
private Object[] condition1;

public Object[] getCondition1 () {
	return condition1;
}
public void setCondition1 (Object... condition1) {
	this. condition1 = condition1;
}

       咱們描述 "person.name like '張%' or person.age = 27 or person.name like '李%' "的代碼變爲:

personCondition.setCondition1("張", 27, "李");
/* 注意參數順序和 condition1 上 @ConditionMapperOrAnnotation 的內部順序一致 */

       因而問題就全解決了。您也許會以爲這個解決方案過於複雜,但對於(A∩B)∪( C∩D)∪( E∩F)來講,用其它代碼方式描述同樣複雜(純sql除外,但咱們知道使用查詢對象代替 sql 的好處)。

       接下來咱們再給出一些常見一點的使用或邏輯的場景,例如我想選擇全部30歲如下或50歲以上的人員:

@Or({
	@ConditionMapperAnnotation(dbFieldName = "age", conditionType = ConditionType.LessThan),
	@ConditionMapperAnnotation(dbFieldName = "age", conditionType = ConditionType.GreaterThan)
})
private Object[] ageFilter;
public Object[] getAgeFilter () {
	return ageFilter;
}
public void setAgeFilter (Object... ageFilter) {
	this. ageFilter = ageFilter;
}

personCondition.setAgeFilter(30,50);

       或者咱們找全部姓張或者姓李的人:

@Or({
	@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike),
	@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike)})
private Object[] nameFilter;
/* 相關getter和setter請自行添加 */

personCondition.setAgeFilter("張", "李");

       或者咱們找年齡在 40 以上或者 level 爲 A 的人:

@Or({
	@ConditionMapperAnnotation(dbFieldName = "age", conditionType = ConditionType.GreaterThan),
	@ConditionMapperAnnotation(dbFieldName = "level", conditionType = ConditionType.Equals)})
private Object[] filter;
/* 相關getter和setter請自行添加 */

personCondition.setFilter(40, "A");

       是否是使用起來仍是挺簡單的?flying的設計哲學是「使您寫下的每一行代碼的回報率最大化」,當您的項目變得愈來愈龐大時,您會愈來愈明顯感覺到這一點。

自定義主鍵生成器

       flying-初雪另外一個特點是增長了自定義主鍵生成器,爲此咱們在flying:insert語句中新增了括號元素,好比:

flying:insert(uuid)                            使用標準uuid做主鍵

flying:insert(uuid_no_line)              使用無下橫線的uuid做主鍵

flying:insert(millisecond)                使用毫秒數做主鍵(需保證每秒併發在1000如下)

       以上這些能夠在 https://gitee.com/limeng32/mybatis.flying/blob/master/src/main/java/indi/mybatis/flying/statics/KeyGeneratorType.java 看到,固然更多的狀況是您會自定義本身的主鍵生成器,只要您的主鍵生成器實現了 flying 中的 indi.mybatis.flying.type.KeyHandler 接口便可,好比這樣調用一個自定義的主鍵生成器類:

flying:insert(indi.mybatis.flying.handlers.MySnowFlakeKeyHandler)

       (上面的 indi.mybatis.flying.handlers.MySnowFlakeKeyHandler 是一個雪花主鍵生成器的 java 版本實現。雪花主鍵生成器由 tweeter 發明用於處理大規模並行寫入,主鍵採用 float 類型存儲以節省資源,自帶遞增無需 order by,單臺主機每秒可產生 400 萬個不一樣主鍵,最多可 1024 臺主機集羣同時工做)

       或者您有某幾個表的主鍵要共享一個連續數列的需求(好比工做流),就能夠開發本身的主鍵生成器。

總結

  • 本文是本人開源項目 flying (地址見 https://www.oschina.net/p/flying)在開發最新版本時須要用到的理論基礎之二。
  • flying 解決或邏輯問題的代碼實現尚未寫完,但理論已經徹底梳理清楚,能夠說完成了90%。
  • 這段時間除了flying外同時進行兩個大項目,整我的感受像脫了一層皮,我不能說太多,只能透露其中一個和熬夜洗尿布有關。
  • 最後請您靜待 flying-初雪 降臨。
相關文章
相關標籤/搜索