关于或逻辑的思考 java
本篇文章咱们来探讨如何使用 flying 的方式来描述带有 ”or” 关键字的 sql 语句(若是您对 flying 还不了解,请参见 https://www.oschina.net/p/flying)。一直以来,flying力求作到的就是,把每一次与数据库交互都变为对象交互,而不是字符串交互,由于对象相比字符串至少有如下好处:git
不一样数据库的 sql 语法有区别,但它们的查询对象相同。sql
对象能够跨语言,能够以 json 方式传输保存。数据库
为了作到以上这些点,做为条件的查询对象必须具备如下特色:json
第一个特性很好理解,例如 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)称为合取项,每一个合取项是不包含∩的表达式。
这个归并是关系型数据库本身也会作的,由于它具备如下好处:
若是一个合取项上存在索引,则先判断索引是否可用,如能利用索引快速得出合取项的值,则能加快判断速度。如: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 台主机集群同时工做)
或者您有某几个表的主键要共享一个连续数列的需求(好比工做流),就能够开发本身的主键生成器。
总结