在我們的hibernate中,除了我們常用的HQL查詢以外,還非常好的支持了原生的SQL查詢,那么我們既然使用了hibernate,為什么不都采用hibernate推薦的HQL查詢語句呢?這是因為HQL查詢語句雖然方便我們查詢,但是基於HQL的查詢會將查詢出來的對象保存到hibernate的緩存當中,如果在我們的一個大型項目中(數據量超過了百萬級),這個時候如果使用hibernate的HQL查詢的話,會一次將我們查詢的對象查詢出來后放到緩存中,這個時候會影響我們的效率,所以當在大型項目中使用hibernate時我們的最佳實踐就是--使用原生的SQL查詢語句,而不使用HQL語句,因為通過SQL查詢的話,是不會經過hibernate的緩存的。接下來我們就來看看hibernate的原生SQL查詢
1.標量查詢
在hibernate中,我們如果使用原生SQL查詢的話,是通過SQLQuery接口進行的,我們首先來看看我們最基本的查詢:
session.createSQLQuery("select * from t_student s").list()
session.createSQLQuery("select ID,NAME,SEX from t_student s").list()
這就創建了最簡單的兩條SQL查詢語句,此時返回的數據庫表的字段值是保存在一個Object[]數組中的,數組中的每個元素就是查詢出來的t_student表中的每個字段值,Hibernate會通過ResultSetMetadata來判斷每個字段值的存放位置以及類型,接下來我們看下標量查詢:
List<Object[]> stus = (List<Object[]>)session.createSQLQuery("select * from t_student s") .addScalar("ID") .addScalar("NAME") .setFirstResult(0).setMaxResults(20) .list();
這個查詢語句指定了查詢的字符串以及返回的字段和類型
這條查詢語句仍然會返回一個Object[]類型的數組,但是此時就不會通過ResultSetMetadata,而是我們明確指定的ID,NAME,SEX,此時雖然查詢語句中有 * 將所有的字段查詢出來,但是這個時候僅僅只會返回我們指定的三個字段值,其類型還是由ResultSetMetada來指定的。
2.實體查詢
①返回一個實體對象
上述的標量查詢返回的裸數據,保存到了Object[]數組當中,我們如果要一個實體對象,將其保存到實體對象時就可以使用 addEntity()方法來實現:
@Test public void testSql1() { Session session = null; try { session = HibernateUtil.openSession(); /* * 原生的SQL語句查詢,會將t_student表的所有字段查出來,存放的一個Object[]數組當中 * 如果希望轉換成實體對象,只需要調用 addEntity(Student.class)即可,這時,會首先匹配 * Student對象里面的屬性是否全部查詢出來,如果沒有,則報錯(如果這個類是實體類) * 注意:如果要調用addEntity方法,這個類必須是實體類,即加了@Entity注解或者在XML中配置了實體類 * 映射關系 */ List<Student> stus = (List<Student>)session.createSQLQuery("select * from t_student s") .addEntity(Student.class) .setFirstResult(0).setMaxResults(20) .list(); for(Student stu : stus) { System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
這時我們就指定了將查詢出來的對象存進Student這個實體類中,注意:如果使用了實體對象,那么在查詢時要將實體對象中有的屬性全部在SQL語句中查詢出來,否則就會報錯。
②返回多個實體對象
有的時候我們可能不只查詢出一個實體對象,在使用連接查詢時候,我們可能需要將幾個表的數據都查詢出來,並存放到對應的實體對象中去,這個時候我們應該怎么寫呢?先看下如下一種寫法:
List<Object[]> stus = (List<Object[]>)session.createSQLQuery("select stu.*, cla.*, spe.*" + " from t_student stu left join t_classroom cla on stu.rid=cla.id" + " left join t_special spe on spe.id=cla.sid where stu.name like ?") .addEntity("stu", Student.class) .addEntity("cla", Classroom.class) .addEntity("spe", Special.class) .setFirstResult(0).setMaxResults(20) .setParameter(0, "%張%") .list();
我們這里通過addEntity的一個重載方法給每個別名指定了一個關聯的實體類,這個時候就會出現字段名沖突的問題,我們的本意是查詢出三個實體對象,分別取得其name屬性,但是name屬性在這三張表中的字段都是name,這個時候查詢出來的三個實體對象中的屬性值都是一樣的。我們如果要解決這個方法,只需要用一個 {} 花括號占位符將每個表的所有屬性值括起來即可。如下:
@Test public void testSql3() { Session session = null; try { session = HibernateUtil.openSession(); /** * 當使用連接查詢查詢多個對象時,可以通過addEntity("alias", XXX.class)方法來根據 * 數據庫表的別名來引入多個實體類,這時如果需要將查詢出來的所有的對象分別存入實體類中, * 只需要在查詢出來的對象上添加 {} 號即可,此時就會自動幫我們分類 */ List<Object[]> stus = (List<Object[]>)session.createSQLQuery("select {stu.*}, {cla.*}, {spe.*}" + " from t_student stu left join t_classroom cla on stu.rid=cla.id" + " left join t_special spe on spe.id=cla.sid where stu.name like ?") .addEntity("stu", Student.class) .addEntity("cla", Classroom.class) .addEntity("spe", Special.class) .setFirstResult(0).setMaxResults(20) .setParameter(0, "%張%") .list(); for(Object[] obj : stus) { Student stu = (Student)obj[0]; Classroom cla = (Classroom)obj[1]; Special spe = (Special)obj[2]; System.out.println(stu.getName() + ", " + cla.getName() + ", " + spe.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
③返回不受hibernate管理的實體對象
我們有時候的需求是這樣的,將每個表其中的一些字段查詢出來,然后存放到一個javabean對象中,但是我們的這個bean對象又不需要存放到數據庫,不需要設置成實體對象,這個時候我們往往會創建一個 DTO 的數據傳輸對象來存放我們要儲存的屬性,例如定義了一個 StudentDTO 對象:
public class StudentDTO { private int sid; // 學生id private String sname; // 學生姓名 private String sex; // 學生性別 private String cname; // 班級名 private String spename; // 專業名 public StudentDTO(){} public StudentDTO(int sid, String sname, String sex, String cname, String spename) { super(); this.sid = sid; this.sname = sname; this.sex = sex; this.cname = cname; this.spename = spename; } ................ }
這個時候我們來看看我們的查詢語句:
@Test public void testSql4() { Session session = null; try { /** * 對於非Entity實體對象的類,我們如果要保持數據,可以通過定義一個DTO對象 * 然后調用setResultTransformer(Transformers.aliasToBean(StudentDTO.class))方法 * 來返回一個不受管的Bean對象 */ session = HibernateUtil.openSession(); List<StudentDTO> stus = (List<StudentDTO>)session.createSQLQuery("select " + "stu.id as sid, stu.name as sname, stu.sex as sex, cla.name as cname, spe.name as spename" + " from t_student stu left join t_classroom cla on stu.rid=cla.id" + " left join t_special spe on spe.id=cla.sid where stu.name like ?") .setResultTransformer(Transformers.aliasToBean(StudentDTO.class)) .setFirstResult(0).setMaxResults(20) .setParameter(0, "%張%") .list(); for(StudentDTO std : stus) { System.out.println(std.getSname() + ", " + std.getCname() + ", " + std.getSpename()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
我們要將查詢出來的這些不同的表的字段存放到一個不是實體對象當中,就可以調用 .setResultTransformer(Transformers.aliasToBean(StudentDTO.class)) 方法來創建一個非受管的 bean 對象,這個時候就hibernate就會將屬性值存放到這個 bean 對象當中,注意:查詢出來的表的字段值存放到bean對象中,是通過調用 bean對象的 setter方法,如果該屬性在bean對象中沒有setter方法,則會報錯。
本篇隨筆主要分析了一下如何來通過原生的SQL語句查詢我們需要的信息,我們一定要記住,當數據量非常大的時候,強烈建議使用原生的SQL去查詢數據,而不要使用HQL來查詢,這樣其實使用hibernate來說效率其實也不差,但是我們的增、刪、改的操作則完全可以交給hibernate來完成。