一个实现类似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(如何判断多个集合相等,包含)

从源码上分析hibernate的load和get之间的区别

一说到hibernate的get和load之间的区别,大多数网上的都会说出如下的区别:get不走缓存,load走缓存;或者get不会使用二级缓存之类,然而这些都是错误的。其实两者没有大多的区别,真正的区别在于二者获取对象的方式,以及如何使用对象上。本文从源码分析上分析两者的具体区别。本方使用的hibernate 版本为3.6.3。

获取对象的API,二者都使用统一的方式调用,如下所示:

来源于sessionImpl
		load的api:
                LoadEvent event = new LoadEvent(id, entityName, false, this);
		fireLoad( event, LoadEventListener.LOAD );
                
               get的api:
		LoadEvent event = new LoadEvent(id, entityName, false, this);
		fireLoad(event, LoadEventListener.GET);

可以看出,二者的api都一样,主要的不一样在于,触发事件的方式不一样。load时使用LoadEventListener.LOAD而get时使用LoadEventListener.GET。我们看看两者的具体不一样:

public static final LoadType GET = new LoadType("GET")
			.setAllowNulls(true)
			.setAllowProxyCreation(false)
			.setCheckDeleted(true)
			.setNakedEntityReturned(false);
	
	public static final LoadType LOAD = new LoadType("LOAD")
			.setAllowNulls(false)
			.setAllowProxyCreation(true)
			.setCheckDeleted(true)
			.setNakedEntityReturned(false);

主要的区别在于:在allowNulls上get允许而load不允许,allowProxyCreation上get不允许而load允许。具体这两者的区别在哪儿,我们进一步地从源码上进行分析。

继续阅读“从源码上分析hibernate的load和get之间的区别”

在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以级联操作”

使用正确的dtd声明和entityResolver避免saxReader联网验证

    在使用许多使用xml配置文件的框架时,都会碰到以下的问题。有时候项目运行起来,需要花费许多的时间,有时候项目甚至还启动不起来。如使用hibernate时,经常报以下的错误:

org.dom4j.DocumentException: XXXXXXX
	at org.dom4j.io.SAXReader.read(SAXReader.java:484)
	at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:2211)

    这是由于在hibernate中在解析xml时,默认会对所需要解析的xml进行validate,即进行验证,验证所写的xml是否符合声明格式要求。在网上搜索相关的帖子,提出的办法就是禁用掉这个验证。但是hibernate解析xml工作是在它的内部进行的,程序上不能对其进行修改;同时,既然hibernate内部使用了这个验证,即有它验证的需要,仅仅禁用掉这个验证并不能解决实际的问题。

    在hibernate3.X版本一直到hibernate3.5版本,在hibernate.xml配置文件中,一直使用的xml声明即是如下:

<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

    而是hibernate3.6以上版本,所写的声明中的url地址就变了(当然以前的还继续有效),变成了

http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd

    这两个声明,在3.6以上版本都是可以接受了。前一声明在3.5及以前版本是正确的。但对于这个声明,如果修改了其中任意一个字符,在启动时都会报错。究其原因,则由于在对xml进行验证时,hibernate提供了一个自己实现的entityResolver。

继续阅读“使用正确的dtd声明和entityResolver避免saxReader联网验证”

通过query解析hibernate中的resultTransformer

    任何包装jdbc的框架,都离不开将最终的数据封装成java对象的一个过程。在jdbc中,取得的数据被封装在resultset中,通过迭代resultset来一次次的取得相应的字段和数据值。数据库框架始终需要解决的问题在于将resultset中的字段名称信息和相应的字段值对应起来,然后封装成对象,最后将所有的对象形成一个集合,并最终返回给调用者。
    任何数据库框架都逃不过这中间的处理逻辑,只不过如何将这些逻辑分散在上下的处理中。在Hibernate中,同样也有类似的东西,这个接口就叫做ResultTransformer。

    Transformer的定义如下:

public interface ResultTransformer extends Serializable {

	public Object transformTuple(Object[] tuple, String[] aliases);

	public List transformList(List collection);
}

    其中第一个方法transformTuple,即是如何处理从数据库查询出来的字段值(可能经过了二次处理)和相对应的字段名称值,比如将字段值和字段名称组合成一个map。字段值,即在查询过程中查询的字段列表,而字段名称即是在查询时select的名称。
    第二个方法transformList,提供了对于从数据库返回结果,进行了封装之后,再对封装之后的数据列表进行最后一次处理。如进行去重等。

    为了说明ResultTransformer在Hibernate中的运用,我们从Hibernate中的查询入手,看ResultTransformer如何在其中运用的(以Hibernate3.6.3版本为例,在代码中不显示不必要的代码)。

继续阅读“通过query解析hibernate中的resultTransformer”