日期:2014-05-16  浏览次数:20457 次

一个数据库表映射成多个Hibernate的实体的问题

在最近一个项目中,由于项目管理的不善,导致了开发人员之间的相对独立的开发,所以就造成了本来是同一个数据库的表,但是映射成了两个Java类,本来相安无事,但是因为需求变动,造成此表要增加很多字段,本着节省工作的思想,于是在其中一个实体类中增加了成员变量,而由另外一个实体类继承之,由此引出了一下问题:

?

数据库表为test

两个实体类分别为testDomain1和testDomain2,其中testDomain2 extends testDomain1

?

在业务代码中,使用QBC查询testDomain1时的count时,是这么写的:

criteria.setProjection(Projections.rowCount()).uniqueResult();

?

在执行时抛出了异常:

org.hibernate.NonUniqueResultException: query did not return a unique result: 2
	at org.hibernate.impl.AbstractQueryImpl.uniqueElement(AbstractQueryImpl.java:758)
	at org.hibernate.impl.CriteriaImpl.uniqueResult(CriteriaImpl.java:431)
	at com.ibm.b2e.framework.base.dao.BaseDaoImpl.pagedQuery(BaseDaoImpl.java:784)
	at com.ibm.b2e.framework.base.dao.BaseDaoImpl.pagedQuery(BaseDaoImpl.java:651)

?而看Hibernate打印出的SQL执行了两次select count(*)的操作,于是猜到是表被映射了两次的问题,想从网上查一下解决方法,但是不知道选择什么关键词,只好自己看源代码,

?

发现在uniqueResult()这个方法中调用的是SessionImpl的list(CriteriaImpl criteria)方法

而list()这个方法中调用了sessionFactory的getImplementors(?String className?)方法,这个方法返回的是一个字符串数组,调试发现,它的值正好是我两个实体类的全类名,后面的代码则是根据这两个类名来执行查询,

?

焦点就在sessionFactory的getImplementors(?String className?)中了,马上冲进去一看,发现其中有一个遍历所有已映射对象的循环操作:

while ( iter.hasNext() ) {
	//test this entity to see if we must query it
	EntityPersister testPersister = (EntityPersister) iter.next();
	if ( testPersister instanceof Queryable ) {
		Queryable testQueryable = (Queryable) testPersister;
		String testClassName = testQueryable.getEntityName();
		boolean isMappedClass = className.equals(testClassName);
		if ( testQueryable.isExplicitPolymorphism() ) {
			if (isMappedClass) return new String[] { className }; //NOTE EARLY EXIT
		}
		else {
			if (isMappedClass) {
				results.add(testClassName);
			}
			else {
				final Class mappedClass = testQueryable.getMappedClass( EntityMode.POJO );
				if ( mappedClass!=null && clazz.isAssignableFrom( mappedClass ) ) {
					final boolean assignableSuperclass;
					if ( testQueryable.isInherited() ) {
						Class mappedSuperclass = getEntityPersister( testQueryable.getMappedSuperclass() ).getMappedClass( EntityMode.POJO);
						assignableSuperclass = clazz.isAssignableFrom(mappedSuperclass);
					}
					else {
						assignableSuperclass = false;
					}
					if (!assignableSuperclass) results.add(testClassName);
				}
			}
		}
	}
}

??看代码的意思,首先是判断当前循环到的class是不是本次查询所查的class,其次再判断Mapped class的一个属性值:testQueryable.isExplicitPolymorphism() 如果此值为true的话,直接就返回一个值只有className的字符串数组;而如果isExplicitPolymorphism()方法返回的值是false的话,那么下面的代码将会继续检查当前循环到的类是否是QBC的类的子类,如果是,则加在字符串数组中一起返回,我所遇到的问题正是后者。

?

看来关键出在isExplicitPolymorphism() 的值上面。

?

查询了Hibernate的sessionFactory初始化代码后,发现此方法的返回值来自于Mapping配置文件,打开Mapping配置文件用代码提示看了一下,<class>元素果然有一个名为polymorphism的属性,此属性有两个候选值:explicit,implicit,在研究此值的实际作用时,发现了JavaEye上原来早已经有一篇文章QBC 解惑,里面很详尽的介绍了此方法,

而对于这样的设计梦秋雨童鞋的解释更是一针见血,非常形象,不得不在此处引用一下:

梦秋雨写道
这个事情不用去看实现,仅从对象层面上理解我觉得也能够得出同样的结论。hibernate的管理下应用似乎得到了一个大的对象池,你去查Person,好比和这个对象池的管理员说“把所有的人都叫出来”。当然会返回池中所有的Person,如果不是,那么,你还当学生是人么?

类比,查询Object类的对象,好比说“芸芸众生啊,都来举个手”,结果可想而知。

?

强人啊强人。。强人啊强人。。。(此处重复30遍)