在mybatis中使用Criteria式条件查询

在使用常规的mybatis时,我们经常碰到的问题就是条件式查询。在一个查询界面,查询条件较多,并且运算符并不总是=时,在后台就需要拼装sql语句。这种处理方式肯定不是使用mybatis的初衷,对于使用了hibernate的我来说,如果mybatis也有一套criteria查询就好了。在具体实现中,我们只需要按照hibernate的处理方式定义好相应的criteria,最后传递给mybatis,其自身处理相应的条件和参数信息,最终返回相应的数据即可。如下一个示例代码所示:

List<Criteria> criteriaList = Lists.newArrayList();
criteriaList.add(Criterias.eq("aaa",111));//等于某个值
criteriaList.add(Criterias.ge("date",new Date()));//大于或等于某个时间
criteriaList.add(Criterias.in("code", new String[]{"a","b","c"}));//代码值在一个集合当中

如果使用这种方式,无疑会大大降低编写表单式查询的代码复杂度。同时,在内部处理中也不需要作任何判断,而直接将生成的sql交给mybatis去执行即可。当然,我们不希望生成的sql连我们自己都看不懂(想一想hibernate生成的sql),最终生成的sql像下面这样即可。

select * from table where aaa = #{aaa1,jdbcType=NUMERIC} and date >= #{date2,jdbcType=TIMESTAMP} and code in (#{code1,jdbcType=VARCHAR},#{code2,jdbcType=VARCHAR},#{code3,jdbcType=VARCHAR})

这是标准的mybatis语句,在进行代码调试和处理时也方便进行查看并处理。那么整个处理逻辑即变成如何处理参数信息,即如下所示的语句

字段名 运算符 #{参数名,jdbcType=字段类型} //filed = #{param1,jdbcType=VARCHAR}

参考 数据库表与java域模型之间的mapping和自动生成(基于mybatis)。我们可以很容易地就完成这个处理。分别处理 字段名 运算符 参数名 字段类型 参数映射即可。

继续阅读“在mybatis中使用Criteria式条件查询”

使用group by和count in语句实现find contains many的另一种做法

接上一篇文章一个实现类似find contains many(many in many)的sql 语句,原文使用了group by,另外使用了子句的count case when来做条件的过滤。经过笔者的再三实践,发现了另一种做法,即直接在子查询中使用count语句和in的组合来实现这种操作。

原要求:查询即有名字叫张三,也有名字叫李四的班的名字。
翻译为:查询班信息,其中要求学生名字中存在张三,李四的名字的人数至少大于等于2。(不考虑名字重复)

根据这个实现,以下即为完整的sql语句,能够完美地实现这个操作:

select * from class t where (select count(1) from student where classid = t.id and name in (张三,李四)) >= 2

这种做法,即是直接在谓语上使用count语句,并对count子句做过滤操作。
本实现暂未考虑效率问题,不知道和上一篇文章,哪个效率更好一些。

一个实现类似find contains many(many in many)的sql 语句

有如下一种需求(使用班级class和学生student表来描述)

找到学生中即有名字叫张三也有名字叫李四的班级,其中参数<名字>表示任意多个名字,即不限仅有两个参数。

在这种需求中,如果仅只有张三和李四两个条件,则sql可以写成如下:

select a.* from class a where 
exists(select 1 from student where classId=a.id and name=张三) 
and exists(select 1 from student where classid=a.id and name=李四)

在以上的条件中,有两个条件,因此有两个exists子句。而如果有更多呢,比如三个或四个以上,那么 这个exists就会更多。在使用以java实现的sql语句中,就需要使用程序(如for循环)来组装sql了。

有一种更好的解决办法如下,即类似一种 (张三,李四)均在指定班级的学生列表中这种理解方式。使用伪码来描述就是

select a.* rom class a inner join a.studentList where a.studentList.names contains(张三,李思)
或
select a.* rom class a inner join a.studentList where (张三,李思)in a.studentList.names

就是这种集合之间包含的例子,即保证一个集合在另一个集合中。然而现在的sql还没有能够直接表示这种的,更多的使用是使用in来表示一个参数值在一个集合中,而不是一个子集合包含一系列指定的参数。

那么反过来呢,我们利用in来处理这种问题,当学生有一个名字满足参数中值的时候就+1,那么符合条件的班级中的对学生计数的值一定就等于参数列表的长度了(这里必须假设参数值是不相同的)。简单的逻辑如下所示:

对每一个班级进行分组
对每一个班级中的学生进行处理
当学生中的名字满足条件,计数值+1
即最终计数值=条件长度的班级信息,此即我们要查找的班级

使用sql来实现,那么整个实现的sql如下所示:

select * from class where id in (
    select a.id from class a inner join student b on b.classId = a.id
    group by a.id 
    having count(case b.name in (张三,李四) then 1 end)=2
)

以上sql在oracle 10g下测试通过,这里利用了count只对有值的数据计数,而对null不计数的特点。
此文参考了以下文章
http://www.itpub.net/thread-1169213-3-1.html(如何判断多个集合相等,包含)

在OneToMany时一定要使用cascade以级联操作

在使用hibernate时,经常会碰到一级联操作的问题,一般来说,在一对多的情况下,都是应该使用级联操作的,不管是级联删除还是添加都应该支持。不过,主导方都应该是多这边,那在进行配置时,在一这边使用下面代码将主导权交给多这一边:

mappedby="xxxxx"

在这种情况下,我们在添加多这一边的数据,都是经常使用以下的代码来操作的(引用班级与学生的关系,其中班级以持久化):

Clazz c = Clazz.getDomain(班级,1);//班级
Student stu = new Student();//学生
stu.setClazz(c);//设置班级属性
stu.save();//保存操作

这样的操作,可以避免将主导权交由班级时,在进行学生信息修改时出现的过多的sql问题。然而使用以下代码时,会发生什么情况呢:

Student stu = new Student();
Clazz c = Clazz.getDomain(班级,1);
stu.setClazz(c);//设置班级属性
c.getStuList().add(stu);//添加学生
c.update();//更新

简单一看,好像一定会级联保存,因为我们已经对班级强制性的更新了嘛,而且班级的stuList在属性里面也应该是dirty数据,应该被flush,触发级联操作了。然后,事实不是这样的,最终会不会保存学生信息,取决于在班级上对学生的cascade属性。只有当cascade为save_update(包括 配置成all)时,才会触发级联操作,否则会出现意想不到的问题。

具体原因就在于,实际对班级的更新操作,并不会触发对于stuList这个脏数据的判断,真正的更新操作发生在session的flush阶段,并且对于级联操作,也并不是由update进行负责的,而是由hibernate内部在flush阶段根据cascade的配置来决定是否在进行级联的。

继续阅读“在OneToMany时一定要使用cascade以级联操作”