天天看點

hibernate查詢方式1 HQL查詢2 QBC查詢3 原生SQL查詢

總的來說,hibernate查詢方式可以分為以下幾種:

HQL,QBC,SQL,QBE,EJBQL

我先來簡單解釋以下這幾種查詢方式,具體後面會講。

HQL:Hibernate Query Language 是面向對象的查詢語言, 它和 SQL 查詢語言有些相似. 在 Hibernate 提供的各種檢索方式中, HQL 是使用最廣的一種檢索方式。

QBC:Query By Criteria比HQL更加面向對象的一種方式,又稱條件查詢。

SQL:原生SQL查詢,通過SQLQuery接口進行的,通過執行Session.createSQLQuery()擷取這個接口

QBE:Query By Example例子查詢

EJBQL:與HQL類似,是HQL的一個子集

後兩種查詢方式用的不多,不再細講,僅對前三種查詢方式進行詳細解釋

1 HQL查詢

HQL的文法和SQL很相似,但是HQL是一種面向對象的查詢語句,它的操作對象是類、執行個體、屬性等,而SQL的操作對象 是資料表、列等資料庫對象。

由于HQL是完全面向對象的查詢語句,是以可以支援繼承、多态等特性。

HQL查詢依賴于Query類,每一個Query執行個體對應一個查詢對象,它的執行是通過Session的createQuery()方法來獲得的。

1.1 HQL查詢步驟

s1:擷取Hibernate Session對象。

s2:編寫HQL語句

s3:以HQL語句作為參數,調用Session的createQuery方法建立查詢對象。

s4:如果HQL語句包含參數,則調用Query的setXxx方法為參數指派。

s5:調用Query對象的list()或uniqueResult()方法傳回查詢結果清單(持久化實體集)

note:在 HQL 中一些關鍵字比如 SELECT ,FROM 和 WHERE 等,是不區分大小寫的,但是一些屬性比如表名和列名是區分大小寫的。

舉個例子:

private void query(){
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        //以HQL語句建立Query對象,執行setString方法為HQL語句的參數指派
        //Query調用list方法通路查詢的全部執行個體
        List list = session.createQuery("select distinct p from Person p where p.name=:name")
                   .setString("name", "chenssy").list();

        //周遊查詢結果
        for (Iterator iterator = list.iterator();iterator.hasNext();) {
            Person p = (Person) iterator.next();
            System.out.println("id="+p.getId()+",age="+p.getAge());
        }
        session.close();
    }
           

上面的程式先編寫HQL語句後,使用Session的createQuery(hql)方法建立一個Query,Query對象使用setXxx方法為HQL語句的參數指派,最後調用list()方法傳回查詢的全部結果。

在這裡Query對象可以連續多次調用setXxx方法為HQL參數指派。這是因為Hibernate Query的setXxx方法的傳回值為Query本身,是以程式建立Query後,可以直接多次調用setXxx方法為HQL語句的參數指派。

Query對象還包含如下兩個方法:

setFirstResult(int firstResult):設定傳回的結果集從第幾條記錄開始

setMaxResult(int maxResult):設定本次查詢傳回的結果數目

這兩個方法用于對HQL查詢實作分頁控制

1.2 HQL vs SQL

HQL 查詢語句是面向對象的, Hibernate 負責解析 HQL 查詢語句, 然後根據對象-關系映射檔案中的映射資訊, 把 HQL 查詢語句翻譯成相應的 SQL 語句。HQL 查詢語句中的主體是域模型中的類及類的屬性。

SQL 查詢語句是與關系資料庫綁定在一起的。SQL 查詢語句中的主體是資料庫表及表的字段。

1.3 HQL的常用API

實體查詢

舉個例子:

String hql = "from User";
Query query = session.createQuery(hql);
List<User> list = query.list();
           

上面的HQL語句将取出User的所有對應記錄。

在HQL語句中,本身大小寫無關,但是其中出現的類名和屬性名必須注意大小寫區分。同時,在Hibernate中,查詢的目标實體存在繼承關系的判定,如果from User将傳回所有User以及User子類的記錄。假設系統中存在User的兩個子類:SysAdmin和SysOperator,那麼該hql語句傳回的記錄将包含這兩個子類的所有資料,即使SysAdmin和SysOperator分别對應了不同的庫表。

Where子句:

如果我們想取出名為“Erica”的使用者記錄,可以通過Where子句加以限定(其中AS可以省略):

where子句中,我們可以通過比較操作符指定甄選條件,如:

=, <>, <, >, <=, >=, between, not between, in ,not in, is, like等。同時,在where子句中可以使用算術表達式。

幾個簡單執行個體:

FROM User user WHERE user.age<
FROM User user WHERE user.name IS null
FROM User user WHERE user.name LIKE 'Er%'
FROM User user WHERE (user.age %  = )
FROM User user WHERE (user.age<) AND (user.name LIKE '%Er')
           

屬性查詢

有時,我們需要的資料可能僅僅是實體對象的某個屬性(庫表記錄中的某個字段資訊)。通過HQL可以簡單的做到這一點。

String hql = "SELECT user.name FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
           

上例中,我們指定了隻需要擷取User的name屬性。此時傳回的list資料結構中,每個條目都是一個String類型的name資料。

我們也可以通過一條HQL擷取多個屬性:

public void test() {
        String hql = "SELECT user.name,user.age FROM User user";
        List list = session.createQuery(hql).list();
        Iterator it = list.iterator();
        while(it.hasNext()){
            Object[] results = (Object[]) it.next();
            System.out.println(results[]+","+results[]);
        }
    }
           

而此時,傳回的list資料結構中,每個條目都是一個對象數組(Object[]),其中依次包含了我們所擷取的屬性資料。

除此之外,我們也可以通過在HQL中動态的構造對象執行個體的方法對這些平面化的資料進行封裝。

String hql = "SELECT new User(user.uName,user.uAge) FROM User user";
        List list = session.createQuery(hql).list();
        Iterator it = list.iterator();
        while(it.hasNext()){
            User user = (User) it.next();
            System.out.println(user);
        }
           

通過在HQL中動态的構造對象執行個體,我們實作了對查詢結果的對象化封裝。此時在查詢結果中的User對象僅僅是一個普通的Java對象,僅用于對查詢結果的封裝,除了在構造時賦予的屬性值外,其他屬性均為未指派狀态。同時,在實體類中,要提供包含構造屬性的構造方法,并且順序要相同。

與此同時,我們也可以在HQL的select子句中使用統計函數或者利用DSITINCT關鍵字,剔除重複記錄。

SELECT COUNT(*),MIN(user.age) FROM User user
SELECT DISTINCT user.name FROM User user
           

實體的更新和删除

String hql = "UPDATE User SET age =  WHERE id = ";
        int result = session.createQuery(hql).executeUpdate();

           

上述代碼利用HQL語句實作了更新操作。對于單個對象的更新也許代碼量并沒有減少太多,但如果對于批量更新操作,其便捷性以及性能的提高就相當可觀。

例如,以下代碼将所有使用者的年齡屬性更改為10

UPDATE User SET age = 

           

HQL的delete子句使用同樣很簡單,例如以下代碼删除了所有年齡大于18的使用者記錄:

DELETE User WHERE age > 

           

不過,需要注意的是,在HQL delete/update子句的時候,必須特别注意它們對緩存政策的影響,極有可能導緻緩存同步上的障礙。

分組與排序

Order by子句

舉個例子:

FROM User user ORDER BY user.name
           

預設情況下是按照升序排序,當然我們可以指定排序政策:

order by子句可以指定多個排序條件:

Group by子句

通過Group by可進行分組統計,如果下例中,我們通過Group by子句實作了同齡使用者的統計:

SELECT COUNT(user),user.age FROM User user GROUP BY user.age

           

通過該語句,我們獲得了一系列的統計資料。對于Group by子句獲得的結果集而言,我們可以通過Having子句進行篩選。例如,在上例中,我們對同齡使用者進行了統計,獲得了每個年齡層次中的使用者數量,假設我們隻對超過10人的年齡組感興趣,可用以下語句實作:

參數綁定

SQL注入

在解釋參數綁定的使用時,我們先來解釋一下什麼是SQL注入。

SQL Injection是常見的系統攻擊手短,這種攻擊方式的目标是針對由SQL字元串拼接造成的漏洞。如,為了實作使用者登入功能,我們編寫了以下代碼:

從邏輯上講,該HQL并沒有錯誤,我們根據使用者名和密碼從資料庫中讀取相應的記錄,如果找到記錄,則認為使用者身份合法。

假設這裡的變量username和password是來自于網頁上輸入框的資料。現在我們來做個嘗試,在登入網頁上輸入使用者名:”‘Erica’ or ‘x’=’x’”,密碼随意,也可以登入成功。

此時的HQL語句為:

此時,使用者名中的OR ‘x’=’x’被添加到了HQL并作為子句執行,where邏輯為真,而密碼是否正确就無關緊要。

這就是SQL Injection攻擊的基本原理,而字元串拼接而成的HQL是安全漏洞的源頭。參數的動态綁定機制可以妥善處理好以上問題。

Hibernate提供順序占位符以及引用占位符,将分别舉例說明:

順序占位符:

String hql = "from User user WHERE user.name = ? AND user.age = ?";
        List<User> list = session.createQuery(hql).setString(, "Erica")
                .setInteger(, ).list();
           

引用占位符:

String hql = "from User user WHERE user.uName = :name AND user.uAge = :age";
        List<User> list = session.createQuery(hql).setString("name", "Erica")
                .setInteger("age", ).list();
           

我們甚至還可以用一個JavaBean來封裝查詢參數。

參數綁定機制可以使得查詢文法與具體參數數值互相獨立。這樣,對于參數不同,查詢文法相同的查詢操作,資料庫即可實施性能優化政策。同時,參數綁定機制也杜絕了參數值對查詢文法本身的影響,這也就是避免了SQL Injection的可能。

引用查詢

我們可能遇到過如下編碼規範:“代碼中不允許出現SQL語句”。

SQL語句混雜在代碼之間将破壞代碼的可讀性,并似的系統的可維護性降低。為了避免這樣的情況,我們通常采取将SQL配置化的方式,也就是說,将SQL儲存在配置檔案中。Hibernate提供了HQL可配置化的内置支援。

我們可以在實體映射檔案中,通過query節點定義查詢語句(與class節點同級):

需要注意的是,我們是将HQL語句寫入到了xml檔案中,是以可能會造成沖突。例如HQL語句的的“<”(小于)與xml的文法有沖突。是以我們會用CDATA将其包裹。

之後我們可以通過session的getNamedQuery方法從配置檔案中調用對應的HQL,如:

Query query = session.getNamedQuery("queryTest");
        List<User> list = query.list();
        for(User user : list){
            System.out.println(user);
        }
           

關聯查詢

關于這部分的内容,參考了很多書上的資料,但都感覺講的不夠清晰,也就是說沒有結合到實際的情況中去。下面将按照一個視訊教程上的順序來介紹關聯查詢。

關于這部分的知識點,是鑒于已經對關聯連接配接查詢有所了解的基礎上,比如懂得什麼是左外連接配接、内連接配接等。

下面就開始總結:

HQL迫切左外連接配接

LEFT JOIN FETCH關鍵字表示使用迫切左外連接配接政策。

首先看一下例子中的實體類,這是一個雙向1-N的映射(關于1-N的映射在之前的部落格中有介紹Hibernate關系映射2:雙向1-N關聯).

實體類:

public class Department {

    private Integer id;
    private String name;
    private Set<Employee> emps = new HashSet<>();
  //省卻get和set方法
}
public class Employee {

    private Integer id;
    private String name;
    private float salary;
    private String email;

    private Department dept;
  //省去get和set方法
}
           

迫切左外連接配接

String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for (Department dept : depts) {
    System.out.println(dept.getName() + "-" + dept.getEmps().size());
}
           

上面的例子中是想得到所有的部門以及其中的員工。我們通過DISTINCT進行去重。

注意的點:

list()方法中傳回的集合中存放實體對象的引用。每個Department對象關聯的Employee集合都将被初始化,存放所有關聯的Employee的實體對象

查詢結果中可能會包含重複元素,可以通過DISTINCT關鍵字去重,同樣由于list中存放的實體對象的引用,是以可以通過HashSet來過濾重複對象。例如:

List<Department> depts = query.list();
depts = new ArrayList<>(new LinkedHashSet(depts));
           

左外連接配接

String hql = "FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Object[]> result = query.list();
System.out.println(result);
for (Object[] objs : result) {
    System.out.println(Arrays.asList(objs));
}
           

注意的是:通過左外連接配接傳回的list是一個包含Department和與之連接配接的Employee的object數組。是以我們還需要對數組進行處理,并且有重複。鑒于此,我們可以通過DISTINCT進行處理。

String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN d.emps";
        Query query = session.createQuery(hql);

        List<Department> depts = query.list();
        System.out.println(depts.size());

        for (Department dept : depts) {
            System.out.println(dept.getName() + ", " + dept.getEmps().size());
        }
           

注意:

list方法傳回的集合中存放的是對象數組類型

根據配置檔案來決定Employee集合的檢索政策,比如是fetch、lazy啊或者怎樣,還不是像迫切左外連接配接那樣。

我們真正進行的過程中,一般都會使用迫切左外連接配接。因為迫切左外連接配接隻發送了一條SQL語句将所有資訊都查出來,而左外連接配接就算不使用集合的對象,也會進行查詢,而當真正使用集合的時候,又會再去查詢。是以性能上迫切左外連接配接要好。

子查詢

子查詢可以在HQL中利用另外一條HQL的查詢結果。

例如:

FROM User user WHERE (SELECT COUNT(*) FROM user.address) > 

           

HQL中,子查詢必須出現在where子句中,且必須以一對圓括号包圍。

2 QBC查詢

2.1 QBC查詢說明

條件查詢一般是通過以下三個類完成的:

1、Criteria:代表一次查詢

2、Criterion:代表一個查詢條件

3、Restriction:代表查詢條件的工具類

執行條件查詢的步驟如下:

1、獲得Hibernate的Session對象

2、以Session對象建立Criteria對象

3、使用Restriction的靜态方法建立Criterion查詢條件

4、向Criteria查詢中添加Criterion查詢條件

5、執行Criterion的list()方法或者uniqueResult()方法傳回結果集

舉個例子:

public void query(){
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        //使用ctiteria進行條件查詢
        List list = session.createCriteria(Person.class)
                    .add(Restrictions.eq("id", ))
                    .list();
        for (Iterator iterator = list.iterator();iterator.hasNext();) {
            Person person = (Person) iterator.next();
            System.out.println(person.getName());
        }
    }
           

Criteria對象本身并不具備任何資料過濾篩選功能,但程式可以通過向Criteria對象中組合多個Criterion(每一個Criterion對象代表了一個資料過濾條件)即可實作資料過濾。

Criteria包含如下兩個方法:

setFristResult(int firstResult):設定查詢傳回的第一行記錄

setMaxResult(int maxResult):設定查詢傳回的記錄數

這兩個方法用于查詢分頁

Criteria還包含如下幾個常用的方法:

add(Criterion criterion):增加查詢條件

addOrder(Order order):增加排序規則

List list = session.createCriteria(Person.class)
  .add(Restrictions.like("name", "李%"))      //增加查詢條件
  .addOrder(Order.desc("name"))               //結果集排序
  .setMaxResults()                  //傳回的記錄數最多為條                         .list();                                 //傳回結果集
           

Criterion接口代表了一個查詢條件,該查詢條件有Restrictions負責産生。Restrictions是專門用于産生查詢條件的工具類,它的方法大部分是靜态方法。

2.2 QBC查詢的限制條件

你可以使用 Criteria 對象可用的 add() 方法去添加一個标準查詢的限制。

以下是一個示例,它實作了添加一個限制,令傳回工資等于 2000 的記錄:

Criteria cr = session.createCriteria(Employee.class);    
cr.add(Restrictions.eq("salary", ));    
List results = cr.list();
           

以下是幾個例子,涵蓋了不同的情況,可按要求進行使用:

Criteria cr = session.createCriteria(Employee.class);

// To get records having salary more than 
cr.add(Restrictions.gt("salary", ));

// To get records having salary less than 
cr.add(Restrictions.lt("salary", ));

// To get records having fistName starting with zara
cr.add(Restrictions.like("firstName", "zara%"));

// Case sensitive form of the above restriction.
cr.add(Restrictions.ilike("firstName", "zara%"));

// To get records having salary in between  and 
cr.add(Restrictions.between("salary", , ));

// To check if the given property is null
cr.add(Restrictions.isNull("salary"));

// To check if the given property is not null
cr.add(Restrictions.isNotNull("salary"));

// To check if the given property is empty
cr.add(Restrictions.isEmpty("salary"));

// To check if the given property is not empty
cr.add(Restrictions.isNotEmpty("salary"));
           

你可以模仿以下示例,使用邏輯表達式建立 AND 或 OR 的條件組合:

Criteria cr = session.createCriteria(Employee.class);

Criterion salary = Restrictions.gt("salary", );
Criterion name = Restrictions.ilike("firstNname","zara%");

// To get records matching with OR condistions
LogicalExpression orExp = Restrictions.or(salary, name);
cr.add( orExp );

// To get records matching with AND condistions
LogicalExpression andExp = Restrictions.and(salary, name);
cr.add( andExp );

List results = cr.list();
           

2.3 分頁使用标準

這裡有兩種分頁标準接口方法:

序号 方法描述
1 public Criteria setFirstResult(int firstResult),這種方法需要一個代表你的結果集的第一行的整數,以第 0 行為開始。
2 public Criteria setMaxResults(int maxResults),這個方法設定了 Hibernate 檢索對象的 maxResults。

利用上述兩種方法結合在一起,我們可以在我們的 Web 或 Swing 應用程式建構一個分頁元件。以下是一個例子,利用它你可以一次取出 10 行:

Criteria cr = session.createCriteria(Employee.class);
cr.setFirstResult();
cr.setMaxResults();
List results = cr.list();
           

2.4 排序結果

标準 API 提供了 org.hibernate.criterion.order 類可以去根據你的一個對象的屬性把你的排序結果集按升序或降序排列。這個例子示範了如何使用 Order 類對結果集進行排序:

Criteria cr = session.createCriteria(Employee.class);
// To get records having salary more than 
cr.add(Restrictions.gt("salary", ));

// To sort records in descening order
crit.addOrder(Order.desc("salary"));

// To sort records in ascending order
crit.addOrder(Order.asc("salary"));

List results = cr.list();  
           

2.5 預測與聚合

标準 API 提供了 org.hibernate.criterion.projections 類可得到各屬性值的平均值,最大值或最小值。Projections 類與 Restrictions 類相似,均提供了幾個擷取預測執行個體的靜态工廠方法。

以下是幾個例子,涵蓋了不同的情況,可按要求進行使用:

Criteria cr = session.createCriteria(Employee.class);

// To get total row count.
cr.setProjection(Projections.rowCount());

// To get average of a property.
cr.setProjection(Projections.avg("salary"));

// To get distinct count of a property.
cr.setProjection(Projections.countDistinct("firstName"));

// To get maximum of a property.
cr.setProjection(Projections.max("salary"));

// To get minimum of a property.
cr.setProjection(Projections.min("salary"));

// To get sum of a property.
cr.setProjection(Projections.sum("salary")); 
           

2.6 标準查詢示例

考慮下面的 POJO 類:

public class Employee {
   private int id;
   private String firstName; 
   private String lastName;   
   private int salary;  

   public Employee() {}
   public Employee(String fname, String lname, int salary) {
      this.firstName = fname;
      this.lastName = lname;
      this.salary = salary;
   }
   public int getId() {
      return id;
   }
   public void setId( int id ) {
      this.id = id;
   }
   public String getFirstName() {
      return firstName;
   }
   public void setFirstName( String first_name ) {
      this.firstName = first_name;
   }
   public String getLastName() {
      return lastName;
   }
   public void setLastName( String last_name ) {
      this.lastName = last_name;
   }
   public int getSalary() {
      return salary;
   }
   public void setSalary( int salary ) {
      this.salary = salary;
   }
}
           

讓我們建立以下員工表來存儲 Employee 對象:

create table EMPLOYEE (
   id INT NOT NULL auto_increment,
   first_name VARCHAR() default NULL,
   last_name  VARCHAR() default NULL,
   salary     INT  default NULL,
   PRIMARY KEY (id)
);
           

以下是映射檔案:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
 "-//Hibernate/Hibernate Mapping DTD//EN"
 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 

<hibernate-mapping>
   <class name="Employee" table="EMPLOYEE">
      <meta attribute="class-description">
         This class contains the employee detail. 
      </meta>
      <id name="id" type="int" column="id">
         <generator class="native"/>
      </id>
      <property name="firstName" column="first_name" type="string"/>
      <property name="lastName" column="last_name" type="string"/>
      <property name="salary" column="salary" type="int"/>
   </class>
</hibernate-mapping>  
           

最後,我們将用 main() 方法建立應用程式類來運作應用程式,我們将使用 Criteria 查詢:

import java.util.List; 
import java.util.Date;
import java.util.Iterator; 

import org.hibernate.HibernateException; 
import org.hibernate.Session; 
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Projections;
import org.hibernate.cfg.Configuration;

public class ManageEmployee {
   private static SessionFactory factory; 
   public static void main(String[] args) {
      try{
         factory = new Configuration().configure().buildSessionFactory();
      }catch (Throwable ex) { 
         System.err.println("Failed to create sessionFactory object." + ex);
         throw new ExceptionInInitializerError(ex); 
      }
      ManageEmployee ME = new ManageEmployee();

      /* Add few employee records in database */
      Integer empID1 = ME.addEmployee("Zara", "Ali", );
      Integer empID2 = ME.addEmployee("Daisy", "Das", );
      Integer empID3 = ME.addEmployee("John", "Paul", );
      Integer empID4 = ME.addEmployee("Mohd", "Yasee", );

      /* List down all the employees */
      ME.listEmployees();

      /* Print Total employee's count */
      ME.countEmployee();

      /* Print Toatl salary */
      ME.totalSalary();
   }
   /* Method to CREATE an employee in the database */
   public Integer addEmployee(String fname, String lname, int salary){
      Session session = factory.openSession();
      Transaction tx = null;
      Integer employeeID = null;
      try{
         tx = session.beginTransaction();
         Employee employee = new Employee(fname, lname, salary);
         employeeID = (Integer) session.save(employee); 
         tx.commit();
      }catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      }finally {
         session.close(); 
      }
      return employeeID;
   }

   /* Method to  READ all the employees having salary more than 2000 */
   public void listEmployees( ){
      Session session = factory.openSession();
      Transaction tx = null;
      try{
         tx = session.beginTransaction();
         Criteria cr = session.createCriteria(Employee.class);
         // Add restriction.
         cr.add(Restrictions.gt("salary", ));
         List employees = cr.list();

         for (Iterator iterator = 
                           employees.iterator(); iterator.hasNext();){
            Employee employee = (Employee) iterator.next(); 
            System.out.print("First Name: " + employee.getFirstName()); 
            System.out.print("  Last Name: " + employee.getLastName()); 
            System.out.println("  Salary: " + employee.getSalary()); 
         }
         tx.commit();
      }catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      }finally {
         session.close(); 
      }
   }
   /* Method to print total number of records */
   public void countEmployee(){
      Session session = factory.openSession();
      Transaction tx = null;
      try{
         tx = session.beginTransaction();
         Criteria cr = session.createCriteria(Employee.class);

         // To get total row count.
         cr.setProjection(Projections.rowCount());
         List rowCount = cr.list();

         System.out.println("Total Coint: " + rowCount.get() );
         tx.commit();
      }catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      }finally {
         session.close(); 
      }
   }
  /* Method to print sum of salaries */
   public void totalSalary(){
      Session session = factory.openSession();
      Transaction tx = null;
      try{
         tx = session.beginTransaction();
         Criteria cr = session.createCriteria(Employee.class);

         // To get total salary.
         cr.setProjection(Projections.sum("salary"));
         List totalSalary = cr.list();

         System.out.println("Total Salary: " + totalSalary.get() );
         tx.commit();
      }catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      }finally {
         session.close(); 
      }
   }
} 
           

編譯和執行

這是編譯并運作上述應用程式的步驟。確定你有适當的 PATH 和 CLASSPATH,然後執行編譯程式。

按照在配置一章講述的方法建立 hibernate.cfg.xml 配置檔案。

如上述所示建立 employee.hbm.xml 映射檔案。

如上述所示建立 employee.java 源檔案并編譯。

如上述所示建立 manageemployee.java 源檔案并編譯。

執行 manageemployee 二進制代碼運作程式。

你會得到下面的結果,并且記錄将會在 EMPLOYEE 表建立。

$java ManageEmployee
.......VARIOUS LOG MESSAGES WILL DISPLAY HERE........

First Name: Daisy  Last Name: Das  Salary: 
First Name: John  Last Name: Paul  Salary: 
First Name: Mohd  Last Name: Yasee  Salary: 
Total Coint: 
Total Salary: 
           

如果你檢查你的 EMPLOYEE 表,它應該有以下記錄:

mysql> select * from EMPLOYEE;
+----+------------+-----------+--------+
| id | first_name | last_name | salary |
+----+------------+-----------+--------+
| 14 | Zara       | Ali       |   2000 |
| 15 | Daisy      | Das       |   5000 |
| 16 | John       | Paul      |   5000 |
| 17 | Mohd       | Yasee     |   3000 |
+----+------------+-----------+--------+
4 rows in set (0.00 sec)
mysql>  
           

3 原生SQL查詢

3.1 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方法傳回查詢的結果集

3.2 标量查詢

最基本的 SQL 查詢是從一個或多個清單中擷取一個标量(值)清單。以下是使用原生 SQL 進行擷取标量的值的文法:

String sql = "SELECT first_name, salary FROM EMPLOYEE";
SQLQuery query = session.createSQLQuery(sql);
query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List results = query.list();
           

它将傳回一個Object數組組成的List,數組每個元素都是EMPLOYEE表的一個字段值。Hibernate會使用ResultSetMetadata來判定傳回的标量值的實際順序和類型。

但是在JDBC中過多的使用ResultSetMetadata會降低程式的性能。是以為了過多的避免使用ResultSetMetadata或者為了指定更加明确的傳回值類型,我們可以使用addScalar()方法:

session.createSQLQuery("SELECT * FROM EMPLOYEE")
.addScalar("name",StandardBasicTypes.STRING)
.addScalar("age",StandardBasicTypes.INT)
.list();
           

這個查詢指定了:

1、SQL查詢字元串。

2、要傳回的字段和類型。

它仍然會傳回Object數組,但是此時不再使用ResultSetMetdata,而是明确的将name和age按照String和int類型從resultset中取出。同時,也指明了就算query是使用*來查詢的,可能獲得超過列出的這三個字段,也僅僅會傳回這三個字段。

如果僅僅隻需要選出某個字段的值,而不需要明确指定該字段的資料類型,則可以使用addScalar(String columnAlias)。

從上面可以看出。标量查詢中addScalar()方法有兩個作用:

1、指定查詢結果包含哪些資料列—沒有被addScalar選出的列将不會包含在查詢結果中。

2、指定查詢結果中資料列的資料類型

3.3 實體查詢

上面的标量查詢傳回的标量結果集,也就是從resultset中傳回的“裸”資料。如果我們想要的結果是某個對象的實體,這是就可以通過addEntity()方法來實作。addEntity()方法可以講結果轉換為實體。但是在轉換的過程中要注意幾個問題:

1、查詢傳回的是某個資料表的全部資料列

2、該資料表有對應的持久化類映射

這時才可以通過addEntity()方法将查詢結果轉換成實體。

String sql = "SELECT * FROM EMPLOYEE";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Employee.class);
List results = query.list(); 
           

這個查詢指定:

1、SQL查詢字元串

2、要傳回的實體

3.4 指定 SQL 查詢

以下是從原生 SQL 查詢中通過 addEntity() 方法和使用指定 SQL 查詢來擷取實體對象整體的文法:

String sql = "SELECT * FROM EMPLOYEE WHERE id = :employee_id";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Employee.class);
query.setParameter("employee_id", );
List results = query.list(); 
           

上面的都是單表查詢,如果我們在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[];
            MyEvent event = (MyEvent) objects[];
            System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle());

        }
    }
           

3.5 關聯和繼承

通過提前抓取将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[];
            MyEvent event = (MyEvent) objects[];
            System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle());

        }
    }
           

上面的程式傳回的Person對象,其屬性myEvent屬性已經完全被初始化,不再需要資料庫的額外操作,同時将該屬性轉換成别名為e的實體。也就是說傳回的結果是Person、Event對象數組的清單。

3.6 命名查詢

我們可以将SQL語句不放在程式中,而是放在配置檔案中。這樣可以更好地提高程式解耦。

Hibernate使用

<!-- 命名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>
           

使用Session的getNamedQuery即可獲得指定命名sql查詢。

public void query(){
        Session session = HibernateUtil.getSession();
        //調用命名查詢,直接傳回結果
        List list = session.getNamedQuery("sqlquery")
                    .setInteger("age", ).list();
        for(Iterator iterator = list.iterator();iterator.hasNext();){
            //每個集合元素都是Person、MyEvent所組成的數組
            Object[] objects = (Object[]) iterator.next();
            Person person = (Person) objects[];
            MyEvent event = (MyEvent) objects[];
            System.out.println("person_id="+person.getId()+" person_name="+person.getName()+" title="+event.getTitle());
        }
        session.close();
    }
           

Hibernate允許吧把結果集的映射資訊放在

<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>
           

3.7 調用存儲過程

Hibernate可以通過命名SQL查詢來調用存儲過程或者函數。對于函數,該函數必須傳回一個結果集;對于存儲過程,該存儲過程的第一個參數必須傳出參數,且資料類型是結果集。

下面是一個簡單的存儲過程:

Create procedure select_person()
Select * from person_inf;
           

如果需要使用該存儲過程,可以先将其定義成命名SQL查詢,然後在程式中使用。

當使用原生SQL來調用存儲過程,應該為

<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>
           

程式與上面相同。

調用存儲過程需要注意以下幾個問題:

為了在Hibernate中使用存儲過程,你必須遵循一些規則.不遵循這些規則的存儲過程将不可用.如果你仍然想要使用他們, 你必須通過session.connection()來執行他們.這些規則針對于不同的資料庫.因為資料庫 提供商有各種不同的存儲過程文法和語義.

對存儲過程進行的查詢無法使用setFirstResult()/setMaxResults()進行分頁。

建議采用的調用方式是标準SQL92:

{ ? = call functionName(<parameters>) }

或者

{ ? = call procedureName(<parameters>}

.原生調用文法不被支援。

對于Oracle有如下規則:

函數必須傳回一個結果集。存儲過程的第一個參數必須是OUT,它傳回一個結果集。這是通過Oracle 9或10的SYS_REFCURSOR類型來完成的。在Oracle中你需要定義一個REF CURSOR類型,參見Oracle的手冊。

對于Sybase或者MS SQL server有如下規則:

存儲過程必須傳回一個結果集。.注意這些servers可能傳回多個結果集以及更新的數目.Hibernate将取出第一條結果集作為它的傳回值, 其他将被丢棄。

如果你能夠在存儲過程裡設定SET NOCOUNT ON,這可能會效率更高,但這不是必需的。

3.8 原生 SQL 的例子

考慮下面的 POJO 類:

public class Employee {
   private int id;
   private String firstName; 
   private String lastName;   
   private int salary;  

   public Employee() {}
   public Employee(String fname, String lname, int salary) {
      this.firstName = fname;
      this.lastName = lname;
      this.salary = salary;
   }
   public int getId() {
      return id;
   }
   public void setId( int id ) {
      this.id = id;
   }
   public String getFirstName() {
      return firstName;
   }
   public void setFirstName( String first_name ) {
      this.firstName = first_name;
   }
   public String getLastName() {
      return lastName;
   }
   public void setLastName( String last_name ) {
      this.lastName = last_name;
   }
   public int getSalary() {
      return salary;
   }
   public void setSalary( int salary ) {
      this.salary = salary;
   }
}
           

讓我們建立以下 EMPLOYEE 表來存儲 Employee 對象:

create table EMPLOYEE (
   id INT NOT NULL auto_increment,
   first_name VARCHAR() default NULL,
   last_name  VARCHAR() default NULL,
   salary     INT  default NULL,
   PRIMARY KEY (id)
);
           

以下是映射檔案:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
 "-//Hibernate/Hibernate Mapping DTD//EN"
 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 

<hibernate-mapping>
   <class name="Employee" table="EMPLOYEE">
      <meta attribute="class-description">
         This class contains the employee detail. 
      </meta>
      <id name="id" type="int" column="id">
         <generator class="native"/>
      </id>
      <property name="firstName" column="first_name" type="string"/>
      <property name="lastName" column="last_name" type="string"/>
      <property name="salary" column="salary" type="int"/>
   </class>
</hibernate-mapping>
           

最後,我們将用 main() 方法建立應用程式類來運作應用程式,我們将使用原生 SQL 查詢:

import java.util.*; 

import org.hibernate.HibernateException; 
import org.hibernate.Session; 
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.SQLQuery;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.cfg.Configuration;

public class ManageEmployee {
   private static SessionFactory factory; 
   public static void main(String[] args) {
      try{
         factory = new Configuration().configure().buildSessionFactory();
      }catch (Throwable ex) { 
         System.err.println("Failed to create sessionFactory object." + ex);
         throw new ExceptionInInitializerError(ex); 
      }
      ManageEmployee ME = new ManageEmployee();

      /* Add few employee records in database */
      Integer empID1 = ME.addEmployee("Zara", "Ali", );
      Integer empID2 = ME.addEmployee("Daisy", "Das", );
      Integer empID3 = ME.addEmployee("John", "Paul", );
      Integer empID4 = ME.addEmployee("Mohd", "Yasee", );

      /* List down employees and their salary using Scalar Query */
      ME.listEmployeesScalar();

      /* List down complete employees information using Entity Query */
      ME.listEmployeesEntity();
   }
   /* Method to CREATE an employee in the database */
   public Integer addEmployee(String fname, String lname, int salary){
      Session session = factory.openSession();
      Transaction tx = null;
      Integer employeeID = null;
      try{
         tx = session.beginTransaction();
         Employee employee = new Employee(fname, lname, salary);
         employeeID = (Integer) session.save(employee); 
         tx.commit();
      }catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      }finally {
         session.close(); 
      }
      return employeeID;
   }

   /* Method to  READ all the employees using Scalar Query */
   public void listEmployeesScalar( ){
      Session session = factory.openSession();
      Transaction tx = null;
      try{
         tx = session.beginTransaction();
         String sql = "SELECT first_name, salary FROM EMPLOYEE";
         SQLQuery query = session.createSQLQuery(sql);
         query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
         List data = query.list();

         for(Object object : data)
         {
            Map row = (Map)object;
            System.out.print("First Name: " + row.get("first_name")); 
            System.out.println(", Salary: " + row.get("salary")); 
         }
         tx.commit();
      }catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      }finally {
         session.close(); 
      }
   }

   /* Method to  READ all the employees using Entity Query */
   public void listEmployeesEntity( ){
      Session session = factory.openSession();
      Transaction tx = null;
      try{
         tx = session.beginTransaction();
         String sql = "SELECT * FROM EMPLOYEE";
         SQLQuery query = session.createSQLQuery(sql);
         query.addEntity(Employee.class);
         List employees = query.list();

         for (Iterator iterator = 
                           employees.iterator(); iterator.hasNext();){
            Employee employee = (Employee) iterator.next(); 
            System.out.print("First Name: " + employee.getFirstName()); 
            System.out.print("  Last Name: " + employee.getLastName()); 
            System.out.println("  Salary: " + employee.getSalary()); 
         }
         tx.commit();
      }catch (HibernateException e) {
         if (tx!=null) tx.rollback();
         e.printStackTrace(); 
      }finally {
         session.close(); 
      }
   }
}
           

編譯和執行

這是編譯并運作上述應用程式的步驟。確定你有适當的 PATH 和 CLASSPATH,然後執行編譯程式。

按照在配置一章講述的方法建立 hibernate.cfg.xml 配置檔案。

如上述所示建立 employee.hbm.xml 映射檔案。

如上述所示建立 employee.java 源檔案并編譯。

如上述所示建立 manageemployee.java 源檔案并編譯。

執行 manageemployee 二進制代碼運作程式。

你會得到下面的結果,并且記錄将會在 EMPLOYEE 表建立。

$java ManageEmployee
.......VARIOUS LOG MESSAGES WILL DISPLAY HERE........

First Name: Zara, Salary: 
First Name: Daisy, Salary: 
First Name: John, Salary: 
First Name: Mohd, Salary: 
First Name: Zara  Last Name: Ali  Salary: 
First Name: Daisy  Last Name: Das  Salary: 
First Name: John  Last Name: Paul  Salary: 
First Name: Mohd  Last Name: Yasee  Salary:   
           

如果你檢查你的 EMPLOYEE 表,它應該有以下記錄:

mysql> select * from EMPLOYEE;
+----+------------+-----------+--------+
| id | first_name | last_name | salary |
+----+------------+-----------+--------+
| 26 | Zara       | Ali       |   2000 |
| 27 | Daisy      | Das       |   5000 |
| 28 | John       | Paul      |   5000 |
| 29 | Mohd       | Yasee     |   3000 |
+----+------------+-----------+--------+
4 rows in set (0.00 sec)
mysql>