在 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-初雪 降临。
相关文章
相关标签/搜索