最近在做一個較為復雜的查詢,hibernate基本的查詢不能滿足,只好使用其提供的原生sql查詢。參考網上的一些資料,做一些總結。
對原生SQL查詢執行的控制是通過SQLQuery接口進行的,通過執行Session.createSQLQuery()獲取這個接口。該接口是Query接口的子接口。
執行SQL查詢步驟如下:
1、獲取Hibernate Session對象
2、編寫SQL語句
3、通過Session的createSQLQuery方法創建查詢對象
4、調用SQLQuery對象的addScalar()或addEntity()方法將選出的結果與標量值或實體進行關聯,分別用於進行標量查詢或實體查詢
5、如果SQL語句包含參數,調用Query的setXxxx方法為參數賦值
6、調用Query的list方法返回查詢的結果集
一、標量查詢
最基本的SQL查詢就是獲得一個標量的列表:
session.createSQLQuery("select * from person_info").list(); session.createSQLQuery("select id,name,age from person_info").list();
它們都將返回一個Object數組組成的List,數組每個元素都是person_info表的一個字段值。Hibernate會使用ResultSetMetadata來判定返回的標量值的實際順序和類型。但是在JDBC中過多的使用ResultSetMetadata會降低程序的性能。所以為了過多的避免使用ResultSetMetadata或者為了指定更加明確的返回值類型,我們可以使用addScalar()方法:
session.createSQLQuery("select * from person_inf") .addScalar("name",StandardBasicTypes.STRING) .addScalar("age",StandardBasicTypes.INT) .list();
這個查詢指定了:
1、SQL查詢字符串。
2、要返回的字段和類型。
它仍然會返回Object數組,但是此時不再使用ResultSetMetdata,而是明確的將name和age按照String和int類型從resultset中取出。同時,也指明了就算query是使用*來查詢的,可能獲得超過列出的這三個字段,也僅僅會返回這三個字段。
如果僅僅只需要選出某個字段的值,而不需要明確指定該字段的數據類型,則可以使用addScalar(String columnAlias)。
public void scalarQuery(){ Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); String sql = "select * from person_info"; List list = session.createSQLQuery(sql). addScalar("person_id",StandardBasicTypes.INTEGER). addScalar("name", StandardBasicTypes.STRING). addScalar("age",StandardBasicTypes.INTEGER).list(); for(Iterator iterator = list.iterator();iterator.hasNext();){ //每個集合元素都是一個數組,數組元素師person_id,person_name,person_age三列值 Object[] objects = (Object[]) iterator.next(); System.out.println("id="+objects[0]); System.out.println("name="+objects[1]); System.out.println("age="+objects[2]); System.out.println("----------------------------"); } tx.commit(); session.close(); }
從上面可以看出。標量查詢中addScalar()方法有兩個作用:
1、指定查詢結果包含哪些數據列---沒有被addScalar選出的列將不會包含在查詢結果中。
2、指定查詢結果中數據列的數據類型
二、實體查詢
上面的標量查詢返回的標量結果集,也就是從resultset中返回的“裸”數據。如果我們想要的結果是某個對象的實體,這是就可以通過addEntity()方法來實現。addEntity()方法可以講結果轉換為實體。但是在轉換的過程中要注意幾個問題:
1、查詢返回的是某個數據表的全部數據列
2、該數據表有對應的持久化類映射
這時才可以通過addEntity()方法將查詢結果轉換成實體。
session.createSQLQuery("select * from perons_inf").addEntity(Person.class).list; session.createSQLQuery("select id,name,age from person_inf").addEntity(Person.class).list();
這個查詢指定:
1、SQL查詢字符串
2、要返回的實體
假設Person被映射為擁有id,name和age三個字段的類,以上的兩個查詢都返回一個List,每個元素都是一個Person實體。
假若實體在映射時有一個many-to-one的關聯指向另外一個實體,在查詢時必須也返回那個實體(獲取映射的外鍵列),否則會導致發生一個"column not found"的數據庫錯誤。這些附加的字段可以使用*標注來自動返回,但我們希望還是明確指明,看下面這個具有指向teacher的many-to-one的例子:
session.createSQLQuery("select id, name, age, teacherID from person_info").addEntity(Person.class).list();
這樣就可以通過person.getTeacher()獲得teacher了。
public void entityQuery(){ Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); String sql = "select * from person_info"; List list = session.createSQLQuery(sql). addEntity(Person.class). //指定將查詢的記錄行轉換成Person實體 list(); for (Iterator iterator = list.iterator();iterator.hasNext();) { Person person = (Person) iterator.next(); //集合的每個元素都是一個Person對象 System.out.println("name="+person.getName()); System.out.println("age="+person.getAge()); } tx.commit(); session.close(); }
上面的都是單表查詢,如果我們在SQL語句中使用了多表連接,則SQL語句可以選出多個數據表的數據。Hibernate支持將查詢結果轉換成多個實體。如果要將查詢結果轉換成多個實體,則SQL字符串中應該為不同數據表指定不同別名,並且調用addEntity()方法將不同數據表轉換成不同實體。如下:
public void multiEntityQuery(){ Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); String sql = "select p.*,e.* from person_inf as p inner join event_inf as e" + " on p.person_id=e.person_id"; List list = session.createSQLQuery(sql) .addEntity("p",Person.class) .addEntity("e", MyEvent.class) .list(); for(Iterator iterator = list.iterator();iterator.hasNext();){ //每個集合元素都是Person、MyEvent所組成的數組 Object[] objects = (Object[]) iterator.next(); Person person = (Person) objects[0]; MyEvent event = (MyEvent) objects[1]; System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle()); } }
三、處理關聯和繼承
通過提前抓取將Event連接獲得,而避免初始化proxy帶來的額外開銷也是可能的。這是通過addJoin()方法進行的,通過這個方法可以將實體的關聯實體轉換成查詢對象。如下:
public void joinQuery(){ Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); String sql = "select p.*,e.* from person_inf as p,event_inf as e where e.person_id=p.person_id"; List list = session.createSQLQuery(sql) .addEntity("p",Person.class) .addJoin("e","p.myEvents") .list(); for(Iterator iterator = list.iterator();iterator.hasNext();){ //每個集合元素都是Person、MyEvent所組成的數組 Object[] objects = (Object[]) iterator.next(); Person person = (Person) objects[0]; MyEvent event = (MyEvent) objects[1]; System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle()); } }
上面的程序返回的Person對象,其屬性myEvent屬性已經完全被初始化,不再需要數據庫的額外操作,同時將該屬性轉換成別名為e的實體。也就是說返回的結果是Person、Event對象數組的列表。
四、命名查詢
我們可以將SQL語句不放在程序中,而是放在配置文件中。這樣可以更好地提高程序解耦。
Hibernate使用<sql-query.../>元素來配置命名SQL查詢,配置<sql-query.../>元素有一個必填的name屬性,該屬性用於指定該命名SQL查詢的名稱。
使用<sql-query.../>元素定義命名查詢時,可以包含如下幾個元素:
<return.../>:將查詢結果轉換成持久化實體
<return-join.../>:預加載持久化實體的關聯實體
<return-scalar.../>:將查詢的數據列轉換成標量值
在使用命名SQL查詢時,不需要調用addEntity()、addScalar()等方法。因為在配置命名SQL查詢時,已經指定了查詢返回的結果信息。
<sql-query name="sqlquery"> <!-- 將p別名轉換為Person實體 --> <return alias="p" class="Person" /> <!-- 將e別名轉換成Event實體 --> <return alias="e" class="MyEvent" /> <!-- 指定將person_inf表的name屬性列作為標量值返回--> <return-scalar column="p.name" type="string"/> select p.*,e.* from person_inf as p,event_inf as e where p.person_id = e.person_id and p.age=:age </sql-query>
public void query(){ Session session = HibernateUtil.getSession(); //調用命名查詢,直接返回結果 List list = session.getNamedQuery("sqlquery") .setInteger("age", 30).list(); for(Iterator iterator = list.iterator();iterator.hasNext();){ //每個集合元素都是Person、MyEvent所組成的數組 Object[] objects = (Object[]) iterator.next(); Person person = (Person) objects[0]; MyEvent event = (MyEvent) objects[1]; System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle()); } session.close(); }
Hibernate允許吧把結果集的映射信息放在<resultset.../>元素定義,這樣就可讓多個命名查詢共有該結果集映射。
<resultset name="person_resultSet"> <return alias="p" class="Person" /> <return-scalar column="p.age" type="int"/> </resultset> <sql-query name="sqlquery" resultset-ref="person_resultSet"> select p.* from person as p </sql-query>
五、調用存儲過程
Hibernate可以通過命名SQL查詢來調用存儲過程或者函數。對於函數,該函數必須返回一個結果集;對於存儲過程,該存儲過程的第一個參數必須傳出參數,且數據類型是結果集。
下面是一個簡單的存儲過程:
Create procedure select_person() select * from person_inf;
如果需要使用該存儲過程,可以先將其定義成命名SQL查詢,然後在程序中使用。
當使用原生SQL來調用存儲過程,應該為<sql-query.../>元素指定callable="true"。
<sql-query name="callProcedure" callable="true"> <return class="Person"> <!-- 將查詢的數據列轉換成實體的屬性 --> <return-property name="name" column="name"/> <return-property name="age" column="age" /> <return-property name="person_id" column="id" /> </return> </sql-query>
參考文檔:http://www.tuicool.com/articles/Iryaum