hibernate的native sql查詢

xiaoluo501395377發表於2013-10-19

在我們的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來完成。

相關文章