在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式条件查询”

数据库表与java域模型之间的mapping和自动生成(基于mybatis)

最近有幸读到《企业架构模式》这本书,需要写作于2003年,已经是十年前,但仔细读过,有些东西现在只知道是这样用,但并不知道为什么要这样做。在看过此书之后,很多东西都能够有一条线进行贯穿,在使用到一些框架时,也知道背后的原因了。
这里面有一篇讲到对象-关系元数据映射的,实际上就是指在一个数据库中一个数据表与一个java中的domain对象之间的映射,在文中提到几种操作,也提到了为什么要这样做。其中,重要的当然是为什么要这样做了,但本篇主要讲期间在mybatis中笔者之前做的一个简单的映射,最终的效果与文中的结果基本上是一致的(因此在进行code时,还是没看过此书,结果发现自己又发明了一个新轮子)。
由于使用到mybatis,所以对模型之间的关系这里并没有涉及,只简单对应于一个数据表一个模型的概念。

通常情况下,我们在数据表中一个数据表user,有2个字段分别为user_name和password.那么在java中,我们会有一个对应的domain文件,如下代码所示:

public class User {
private String userName;
private String password;
}

这里只是一个简单的对应,同时字段user_name对应于userName,这里并不是完全相同的字符串.因此,在mybatis相对应的xml中,我们需要显示的对待mapping操作.如下xml所示:

<insert>
insert into user(user_name,password) values(#{userName},#{password});
</insert>

<select columnMap=tMap> <!-- tMap中需要定义mapping关系 >
select user_name,password from user
</select>

这里涉及到一个东西,就是我们需要手动地编写相应的mapping语句,而且涉及到多个地方.比如在insert脚本中,需要编写user_name和userName不同的语句;在select中,还需要手动进行columnMap工作.对于一般的开发人员,使用copy&paste时,这里就会出错.而且一旦涉及到模型属性的变更,比如增加一个属性,表中加一个字段,这里的修改量就较大了,而且一旦涉及到代码还不集中,那就更麻烦了.
本篇即是引入一种特殊的columnMapping对象,并通过自动生成+动态SQL构建,来完成这种操作.参考如下一个insert语句:

	<insert>
		insert into ${table.schema()}.${table.name()}(
		<foreach collection="columnMappingList" separator="," item="cm">
			${cm.jdbcField}
		</foreach>
		) values(
		<foreach collection="columnMappingList" separator="," item="cm">
			#{e.${cm.javaProperty},jdbcType=${cm.jdbcType}}
		</foreach>
		)
	</insert>

继续阅读“数据库表与java域模型之间的mapping和自动生成(基于mybatis)”

在Mybatis中使用接口继承实现通用性crud

在最新的mybatis中,我们通过定义一个接口,然后通过mybatis-spring插件来调用相应的接口xml实现spring代理bean。那么考虑这样一种场景,我们定义了一个通用性的操作,如getByKey方法,即通过一个主键查找所对应的对象。这个在hibernate中可以这样来描述,即

//Hibernate
get(Serializable id,Class clazz)

//我们要实现的Mybatis版本
getByKey(Key key)

由上的调用相对比一下,会发现下面的方法缺少一个clazz,即实际要返回哪个对象。这也是在mybatis中所必须的参数信息,即在mapper.xml中,我们必须定义一个resultType,即标明要返回哪个对象。当然,可以使用通用的map,或者object对象,但是这样的实现就相比hibernate来说,弱很多了。

本文描述了一种,通过接口继承来实现统一的SQL方法,便根据不同的调用者来返回不同的数据对象的一个方法。通过这个方法,可以实现如hibernate中一样的通用性get操作,而不需要每一个数据对象写一个获取方法。

继续阅读“在Mybatis中使用接口继承实现通用性crud”

hibernate中的hql不支持sum(distinct)语句

在进行数据统计时,经常会使用聚合函数,在hibernate中也支持聚合函数。如进行以下的统计查询:

select sum(distinct 奖牌) from 成绩 inner join 参赛运动员 inner join 运动员的学校 group by 学校

在上面的数据模型中,成绩与参赛运动员为一对多关系,即可能为多个运动员以团体参赛的形式参加一个项目,最终取得一条成绩。运动员与学校为多对一关系。在上面的查询中,为避免在同一个成绩中,由团体参赛的运动员来自同一个学校,因为只能记为一个奖牌,而不是N(N为参赛运动员数量)。因此,需要使用distinct对重复的成绩进行过滤,并按学校分组。

在正常的sql查询下,现在的数据库已经支持在sum聚合函数中进行distinct操作了。而在hibernate中,使用如此的查询会报一个如下的错误:

org.hibernate.hql.ast.QuerySyntaxException: unexpected token: distinct

即不支持在sum函数中带有distinct语句,在相关的bug列表中。网页:https://hibernate.onjira.com/browse/HHH-6311 也描述了这一问题,且值到现在还没有解决。那么为什么会产生这个错误呢,这是由hibernate将hql转换为sql的过程中产生的,即在hibernate的语法树中,就不支持这样的写法。相关的语法描述中,针对聚合函数是这样的(在源文件hql.g中)

:aggregate
	: ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! additiveExpression CLOSE! { #aggregate.setType(AGGREGATE); }
	// Special case for count - It's 'parameters' can be keywords.
	|  COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr ) ) ) CLOSE!
	|  collectionExpr
	;

在上面的描述中,只有count支持带distinct或all描述语句,而其它的如sum,avg,max,min均不支持,因此在进行转换时即会产生异常。因为,只能采用sql的方法实现上面的统计查询了。好在使用session.createSQLQuery,只需要将hql进行简单的人工转换再加上addScalar进行结果类型转换即能实现操作,实现上难度不大。

参考文章:Antlr–看Hibernate3如何解释HQL语言

Hibernate中criteria一对多关联查询时distinct的分页和数量问题

数据模型
班级:clazz(id,name,studentList)
学生:student(id,name,address,clazz)

经典查询
查询有地址在成都的班级信息,并分页显示。

一般情况下,以上的条件更多,也更复杂,且查询主体必须从班级入手。那么,使用Hibernate的criteria查询的话,一般情况下,编码如下:

DetachedCriteria criteria = DetachedCriteria.forClass(Clazz,"c");
criteria.createAlias("studentList", "sl");
criteria.add(Restrictions.eq("sl.address", "成都");

//保证Distinct
criteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY);

//分页
listByCriteria(criteria,page);

单就逻辑本身,看似没有什么问题,且在数据较少(如没有分页数据时),会返回正确的结果。但是一旦出现分页数据时,你就会发现,分页数据是错的。表现结果为page对象所表现的数据总数明确不对,且每页所显示的数据也并不是分页信息中的20条,而是不确定的几条(明显少于默认的20条)。这就表明,在这个查询中,肯定有一步出问题了。

没错,在这个查询中,我们要求返回的主体为班级,即Clazz。但Hibernate出生的sql却并不能如我们所愿,它直接使用连接来组装sql语句,并尝试返回包括学生在类的一个复合对象,然后再组装成班级对象,最后根据集合信息进行distinct操作,最后就形成了不正确的结果。整个操作看起来如下所示:

//第一步:生成sql,使用联接查询
select a.*,b.* from clazz a inner join student b on b.aId = a.id where b.address = '成都'

//第二步:带参数 page信息,查询前20条,返回结果为 page.setTotal(XX条),当页数据条数:20
班级一 李一 成都
班级一 李二 成都
班级一 李三 成都
班级二 李四 成都
班级二 李五 成都
班级三 李六 成都
......
//第三步:组装班级对象,去重,此步操作在hibernate程序中进行,返回结果
班级一
班级二
班级三

//返回结果,实际情况:当前page显示数量:20,实际所看到数量 3!

继续阅读“Hibernate中criteria一对多关联查询时distinct的分页和数量问题”

使用hibernate更新和删除时不能使用关系联接操作

在使用hibernate进行更新时,经常会碰到以下的操作。如将name为xxx的a下的bList的code信息更新为code+'f'。在使用hibernate时,经常会写下以下的hibernate操作:

update b set b.code='f' + b.code where b.a.name='xxx'

然而,这句看似没有错误的hql语句,在hibernate进行运行时,却会产生一个奇怪的现象。在生成的hql中,会在表b后面出现一下奇怪的逗号,会报一个错误的sql解析错误的异常,并且在生成的hql中,所没有出现在链接时所使用的a。

原因在于hibernate在进行更新以及删除时候时,并不支持联接操作,包括含有隐式的联接也是不行的。在相应的hibernate jira界面,可以看到以问题的错误:https://hibernate.onjira.com/browse/HHH-2408。在官方的hibernate手册时,有一条很不起眼的描述。http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/batch.html#batch-direct。描述的描述如下:

15.4. DML-style operations
Some points to note: 
No joins, either implicit or explicit, can be specified in a bulk HQL query. Sub-queries can be used in the where-clause, where the subqueries themselves may contain joins. 

继续阅读“使用hibernate更新和删除时不能使用关系联接操作”