文档章节

在 flying 中实现或逻辑的思考

limeng32
 limeng32
发布于 2017/12/25 17:38
字数 2350
阅读 115
收藏 2

关于或逻辑的思考  

     本篇文章我们来探讨如何使用 flying 的方式来描述带有 ”or” 关键字的 sql 语句(如果您对 flying 还不了解,请参见 https://www.oschina.net/p/flying)。一直以来,flying力求做到的就是,把每一次与数据库交互都变为对象交互,而不是字符串交互,因为对象相比字符串至少有以下好处:

  • 对象是完全解析的。比如我有一个子容器向父容器发送查询请求,然后按照业务,父容器要在这个查询请求上修改一个条件再追加一个条件。使用查询对象可以轻松做到这一点,因为它能被完全解析;但解析字符串就很麻烦了(试想一下拆分一个充满了 and 和 or 的 sql),估计只有数据库厂商才能提供可靠的工具。
  • 不同数据库的 sql 语法有区别,但它们的查询对象相同。

  • 对象可以跨语言,可以以 json 方式传输保存。

 为了做到以上这些点,作为条件的查询对象必须具有以下特点:

  1. 各条件变量的赋值顺序无关。
  2. 易于理解和修改。
  3. 各条件变量是“且”的关系。

       第一个特性很好理解,例如 personCondition.setNameLike(“张”); 和 personCondition.setAge(30); 就是顺序无关的,flying查询对象目前所有的条件赋值语句(包括判断条件、分页条件、排序条件)都是顺序无关的。 

       第二个特性是,用户一眼看到某个变量赋值语句就知道它的作用是什么,并可以按需要进行修改。

       第三个特性是,所有的条件变量其实都是用与逻辑“and”相连的。

       看到这里,大家会发现,其实 flying 查询对象只解决了一半的问题,因为对于或操作 “or”,以前根本就没有提及,而没提及的原因是,在满足以上三点的基础上解决或逻辑比较困难。而本文则尝试解决这一问题。

       首先,我们抛出一个足够复杂的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-初雪 降临。

© 著作权归作者所有

limeng32

limeng32

粉丝 3
博文 5
码字总数 8770
作品 1
东城
高级程序员
私信 提问
加载中

评论(0)

mybatis 插件组 flying-阳春 发布

flying 是一个可以极大增加 mybatis 开发速度的插件组,它提供了一种全新的操作数据的方式,目前更新到“阳春”版本。 mybatis 版本与 flying-阳春 的对应关系见下: mybatis 版本 flying 版...

limeng32
2018/04/09
1.5K
2
flying-初雪 发布,mybatis 插件组

flying 是一个可以极大增加 mybatis 开发速度的插件组,它提供了一种全新的操作数据的方式,希望能对您有所帮助。 众所周知,mybatis 虽然易于上手,但放到互联网环境下使用时,不可避免的要...

limeng32
2018/01/22
672
2
mybatis 插件 flying-清明 发布

flying 是一个可以极大增加 mybatis 开发速度的插件,它提供了一种全新的操作数据的方式,目前更新到 “清明” 版本。 mybatis 版本与 flying-清明 的对应关系见下: mybatis 版本 flying 版...

limeng32
2019/04/14
1.3K
1
limeng32/mybatis.flying

mybatis.flying 项目介绍请见 flying-doc.limeng32.com ,国内代码托管网站请见 gitee.com/limeng32/mybatis.flying,我们为开发最好的 mybatis 插件而努力。 flying 是一个可以极大增加 my...

limeng32
2017/10/19
0
0
MyBatis 的扩展插件--flying

flying 是一个可以极大增加 mybatis 开发速度的插件组,它提供了一种全新的操作数据的方式,希望能对您有所帮助。 众所周知,mybatis 虽然易于上手,但放到互联网环境下使用时,不可避免的要...

limeng32
2017/10/10
1.8K
3

没有更多内容

加载失败,请刷新页面

加载更多

《Java 8 in Action》Chapter 11:CompletableFuture:组合式异步编程

某个网站的数据来自Facebook、Twitter和Google,这就需要网站与互联网上的多个Web服务通信。可是,你并不希望因为等待某些服务的响应,阻塞应用程序的运行,浪费数十亿宝贵的CPU时钟周期。比...

后端小哥
32分钟前
63
0
「升级指南」Spring Cloud Alibaba v2.2.0 升级问题整理

下面总结一下由 Spring Cloud Alibaba v2.1.0 升级至 v2.2.0 遇到的问题。 主要问题涉及 sentinel 的问题。 破坏性 ,不向下兼容 Spring Cloud Alibaba Sentinel 不再依赖 sentinel-web-ser...

即将秃头的Java程序员
39分钟前
57
0
【word 2019 for Mac实用教程】word文档如何快速完成给章节标题自动编号?

在编辑word文档时,我们经常会遇到给段落编号,手动编号过程繁琐易出错。 那么,如何快速完成给word文档编号呢? 1、打开一个word 2019 for Mac文档,在【开始】选项卡里选择【多级列表】,在...

mac小叮当
40分钟前
44
0
【tty】应用程序调用write写串口调用流程

这几天在跟进串口使能流控后收发异常问题,特简单梳理了下应用程序执行write操作的调用流程,在这简单记录下,平台为全志方案 tty_io.c tty_io.c n_tty.c serial_core.c sunxi_uart.c tty_w...

DerrickOwen
42分钟前
39
0
当真正的市场危机到来时,避险新贵比特币却惨遭遗忘?

年初肆虐新冠病毒疫情在中国的强制隔离处理下已经得到了有效控制,非湖北地区新增病例速度已经得到了有效控制,全国范围内的存量病例也出现了连续近一周的持续下降。 不过上周五以来中国以外...

daxiongdi
47分钟前
63
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部