-源于我對它的一個誤會
sparta-紫杉 2010-4-14 9:02
開發環境: eclipse3.4.2 + weblogic10.3 + jdk1.6.0_13 + Struts2.1.8 + Spring3.0.1 + Hiberante3.3.2
初識Hibernate3的查詢結果返回Map的功能是非常高興的,因為我第一印象認為它能夠解決類似以下的問題:
從角色表SysRoles中,得到以Role_Id(角色ID)為Key, 以Role_Desc(角色描述)為Value的Map。
要知道在以前,我通常采用如下的傳統方式解決此類問題:

public HashMap getRolesMap()
{

try
{

List<SysRoles> results = getHibernateTemplate().find("from SysRoles");

Map hashMap = new HashMap(0);

for( SysRoles role : results )
{
hashMap.put( role.getRoleId(), role.getRoleDesc());
}

return hashMap;

} catch (RuntimeException re)
{

throw re;
}
}
代碼雖也不多,短短5行代碼,但在代碼里面需要經過一次提取,一次加工的過程也有點繁瑣,程序員嘛,要本著一個“代碼簡潔、清晰”的原則。
有無更好的方法呢?那么在知道Hibernate3能夠從查詢結果中返回Map的功能后,我很自然的寫下如下的代碼,以為能節省一個加工的過程:

public HashMap getPermissionMap()
{

try
{

HashMap hashMap = (HashMap)getSession()
.createSQLQuery("select role_Id, role_Desc from Sys_Roles").setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
return hashMap;

} catch (RuntimeException re)
{

throw re;
}
}
哈哈,只有兩行代碼便能解決這種問題,看起來似乎是理想的選擇,趕快試試吧。
運行后,在jsp偌大的空白頁面上,一行異常代碼分外刺眼: org.hibernate.impl.SQLQueryImpl cannot be cast to java.util.HashMap.
看來setResultTransformer是不能直接返回Map的,它是返回一個封裝了所有Map的List。
看來我還真是太草率了,對上面語句中的“Transformers.ALIAS_TO_ENTITY_MAP”迷感了雙眼。
看來事情并不那么簡單,還需要探索,既然已經開始了,索性探索個明白吧!在經過對代碼、對查詢方法進行幾次修訂后,成為如下代碼:

public HashMap getPermissionMap()
{

try
{
List list = getHibernateTemplate()
.find("select new Map(sysRoles.roleId as roleId, sysRoles.roleDesc as roleDesc) from SysRoles sysRoles");


for( Object map : list)
{
hashMap.putAll( (Map) map );

}

return (HashMap) hashMap;

} catch (RuntimeException re)
{

throw re;
}
}
修改完成之后,我不禁啞然失笑,之前堅持的所謂的“代碼簡潔、清晰”原則已經蕩然無存。但反觀代碼,似乎有些道理,同時,對java的使用也有了更進一步的理解,對浪費的時光也不再那么憐惜了。
可惜事情總不能如你所愿,通過對代碼的執行發現,數據庫里面有多條數據,但是那個返回的hashMap的size卻始終是一條。
我通過下面代碼對返回的hashMap進行循環打印,發現僅能打印一條,并且內容始終是一樣:

for( Map.Entry map : hashMap.entrySet() )
{
System.out.println("#### map1.getKey() = " + map1.getKey() + ", map1.getValue()=" + map1.getValue());
}
唉,還是找找相關的“從Hibernate的查詢結果中返回Map”的原理吧,看來我們多少得為我們的草率付出點代價。
下面是我從Hibernate的教程中得到的相關信息,一個說得比較清楚的是javaEye(現在為itEye)的蔡華江,請原諒使用了你的文章:
//////////////////////////////////////////////////// 摘抄開始 ///////////////////////////////////////////
在問答里和論壇中,經常看到有人問,怎樣將使用本地SQL查詢出來的結果映射為值對象的問題,這里就Hibernate中提供的方法做個結論。
前提,這里沒有使用屬性的延遲加載技術。
假設有個值對像,如下:
package test;


public class Person
{
private Long id;
private String name;
private Long age;
private Long phone;
private String address;


public Person(Long id, String name, Long age, Long phone, String address)
{
this.id = id;
this.name = name;
this.age = age;
this.phone = phone;
this.address = address;
}


public Long getId()
{
return id;
}


public void setId(Long id)
{
this.id = id;
}


public String getName()
{
return name;
}


public void setName(String name)
{
this.name = name;
}


public Long getAge()
{
return age;
}


public void setAge(Long age)
{
this.age = age;
}


public Long getPhone()
{
return phone;
}


public void setPhone(Long phone)
{
this.phone = phone;
}


public String getAddress()
{
return address;
}


public void setAddress(String address)
{
this.address = address;
}
}
如果查詢全部五列記錄的話,那么只要
List list = getHibernateTemplate().loadAll(Person.class);
如果只需要查詢id,name,age三列記錄呢?那么就要新增構造方法了,

public Person(Long id, String name, Long age)
{
this.id = id;
this.name = name;
this.age = age;
}
然后呢,就可以通過HQL來進行查詢。
List list = getHibernateTemplate().find("select new test.Person(id,name,age) from Person");
這個方法通常可以滿足需要了,只是如果,只需要查詢id,name,phone三列記錄的話,還新增構造方法?不行了,會出現構造方法沖突了。有個辦法:
List list = getSession().createQuery("select id,name,phone from person")
.addScalar("id",Hibernate.Long).addScalar("name").addScalar("phone",Hibernate.Long)
.addEntity(Person.class);
但是,這個只適用于存在Person實體的,如果Hibernate中沒有進行Person映射的呢,系統中只存在一個JavaBean。
List list = getSession().createSQLQuery("select id \"id\",name \"name\",phone \"phone\" from person")
.addScalar("id",Hibernate.Long).addScalar("name").addScalar("phone",Hibernate.Long)
.setResultTransformer(Transformers.aliasToBean(Person.class)));
那么Hibernate就會自動將查出來的三列內容組裝到VO對象中去,只是代碼量有點大,而且名稱都需要重新定義為小寫的,在Oracle中查出來的列都默認為大寫的(不知道其它數據庫怎么樣),這個辦法就不依賴于構造方法了,只需要定義私有變量,設置getter/setter方法就行了。
不過如果更猛點的,根本就沒有JavaBean對象可以填充怎么辦,Hibernate可以將查出來的列組裝到集合類中去。如Map。
List list = getSession().createSQLQuery("select * from person")
.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
//sparta-紫杉 注:這個ALIAS_TO_ENTITY_MAP的含義是指以數據庫里的字段為key,以字段內容為Value的Map。有幾個字段就會生成幾個Map,然后將所有的Map組裝到List中。
除了這個Transformers還可以將列轉化為List。
List list = getSession().createSQLQuery("select * from person")
.setResultTransformer(Transformers.T0_LIST);
到此,還可以通過繼承Transformers將結果映射為其它對象,不累述了,基本功能夠用了。
//////////////////////////////////////////////////// 摘抄結束 ///////////////////////////////////////////
哈哈,各位達人,看完上面這篇文章之后,豁然開朗了吧?
請注意在上面文章中筆者注釋的一段文字就會明白:在hibernate從查詢結果返回Map中,是以字段為key,以字段的內容為Value的Map,而不是我想象的假設我在查詢中僅提供兩個字段,那么Hibernate就會自然地以我的第一個字段的內容為key,以第二個字段的內容為Value生成Map列表。
那么我也終于明白為什么在我的執行結果中僅有一條數據的原因了,因為在我的代碼中返回的list里面,雖然有很多條數據,但是我們應該明白一點:
當我使用Map的putAll時,由于在Map中,均是以字段名稱作為key的,那么在數據庫中無論有多少條的記錄,字段名始終是相同的,也就是說,在Map中重復的key會被反復覆蓋,僅保留最后的一條。 它是返回一個封裝了所有Map的List,在使用時,只有迭代該List,提取各Map進行使用才不會導致重復覆蓋。
什么樣的應用場景賦予我們什么樣的想象力,誤會有時源于無知,但更多源于我們的無限想象力,有時異想天開的靈感確實能為我們提供打開另一扇未知知識大門的鑰匙。
難道不是嗎? 本次誤會讓我對Hibernate的理解又深入了一層,無論是原理還是應用場景。再者,不妨將牛吹大一點吧:希望Hibernate團隊能夠從我的這個誤會中吸取點靈感,開發出一個能夠返回以前后兩個字段的內容分別為key和value的Map的方法來,以方便各位使用Hibernate的程序達人,似乎也未嘗不可吧? 哈哈!
-東營 sparta-紫杉 原創,轉載請注明出處 :)
http://www.tkk7.com/SpartaYew/
SpartaYew@163.com
QQ:22086526