Hibernate的基礎

Hibernate基礎語義
基礎配置
Hibernate O/R映射
數據關聯
Hibernate數據檢索
HQL實用技術

Hibernate基礎語義
Configuration
SessionFactory
Session

Configuration
正如其名,Configuration類負責管理Hibernate的配置信息。Hibernate運行時需要獲取一些底層的基本信息,其中幾個關鍵屬性包括:
       
加圖config

Configuration的使用:
//Hibernate框架自動加載CLASSPATH中的hibernate.cfg.xml文件
Configuration cfg = new Configuration().configure();
//Hibernate框架加載指定的hibernate.cfg.xml文件
Configuration cfg = new Configuration().configure(path);

SessionFactory
SessionFactory負責創建Session實例。我們可以通過Configuation實例構建SessionFactory:
Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Configuration實例cfg會根據當前的數據庫配置信息,構造SessionFactory實例並返回。SessionFactory一旦構造完畢,即被賦予特定的配置信息。
也就是說,之後cfg的任何變更將不會影響到已經創建的SessionFactory實例。如果需要使用基於改動後的cfg實例的SessionFactory,需要從cfg重新構建一個SessionFactory實例。同樣,如果應用中需要訪問多個數據庫,那麼針對每個數據庫,應分別爲其創建對應的SessionFactory實例。

Session
Session是Hibernate持久化操作的基礎。注意這裏Session的含義,它與傳統意義上Web層的HttpSession並沒有什麼關係。Hibernate Session之與Hibernate,相當於JDBC Connection相對與JDBC。
Session作爲貫穿Hibernate的持久化管理器核心,提供了衆多持久化方法,如save、update、delete、load等。通過這些方法,我們即可透明地完成對象的增刪改查(CRUD)。
同時,值得注意的是,Hibernate Session的設計是非線程安全的,也就是說,一個Session實例同時只可由一個線程使用,同一個Session實例的多線程併發調用將導致難以預知的錯誤。
Session實例由SessionFactory構建:
Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory=cfg.buildSessionFactory();
Session session= sessionFactory.openSession();
之後我們就可以調用Session所提供的save、update、delete、load等方法完成持久層操作。

基礎配置
在“快速起步”部分,我們已經涉及到Hibernate配置文件的相關內容。爲了達到最簡明的效果,之前的示例中我們僅僅引入了一些最基本的配置條目。而在此之外,Hibernate還提供了更加豐富多樣的選項設定,我們可以結合實際情況,根據應用特點對這些選項進行調整,以獲得更加符合需求的匹配模式和更好的性能表現。
運行期生成的SQL:
<property name="show_sql">true</property>
數據庫連接池的配置:
<property name="connection.pool_size">10</property>
事務管理:
<property name="transaction.factory_class">
    org.hibernate.transaction.JDBCTransactionFactory
</property>

Hibernate O/R映射
O/R映射關係無疑是ORM框架中最爲關鍵的組成部分,也是日常開發中我們必須時刻關注的內容。我們將就Hibernate日常開發中最爲關鍵的映射關係進行探討。

 加入圖片RO圖片

類/表映射配置
<class name="User1" table="user1" catalog="sample">
    ……
</class>

參數    描述
name    類名
table    類對應的表名
catalog    數據庫目錄

id映射配置
<id name="id" type="java.lang.Integer">
    <column name="id" />
    <generator class="assigned" />
</id>

參數    描述
name    映射類中對應主鍵的屬性名
type    上述屬性的數據類型
column    主鍵字段名
class    主健生成方式

assigned、increment、identity、sed.hex、foreign

屬性/字段映射配置
<property name="name" type="java.lang.String">
    <column name="name" length="45" not-null="true" />
</property>

參數    描述
name    映射類屬性名稱
type    上述屬性的數據類型
column    對應數據庫表字段名
length    數據庫類型長度
not-null    字段是否允許爲空

複合主鍵
Hibernate中,通過composite-id節點對複合主鍵進行定義。
爲了說明覆合主鍵的使用,我們以user2表爲藍本,將其name屬性拆分爲兩部分:firstname、lastname,以這兩個字段作爲複合主鍵,由此得到user3表。
CREATE TABLE `sample`.`user3` (
  `firstname` VARCHAR(45) NOT NULL DEFAULT '',
  `lastname` VARCHAR(45) NOT NULL DEFAULT '',
  `age` INTEGER NOT NULL DEFAULT 0,
  PRIMARY KEY(`firstname`, `lastname`)
)
舉例:
主鍵類
package fire;

public class User3Id implements java.io.Serializable {
    private String firstname;
    private String lastname;
    public User3Id() {}
    public User3Id(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }
    ……
    public boolean equals(Object other) {
        ……
    }
    public int hashCode() {
        ……
    }
}

實體類
package fire;

public class User3 implements java.io.Serializable {
    private User3Id id;
    private Integer age;
    public User3() {
    }
    public User3(User3Id id, Integer age) {
        this.id = id;
        this.age = age;
    }
    ……
}

Hibernate映射文件
<hibernate-mapping>
    <class name="fire.User3" table="user3" catalog="sample">
        <composite-id name="id" class="fire.User3Id">
            <key-property name="firstname" type="java.lang.String">
                <column name="firstname" length="45" />
            </key-property>
            <key-property name="lastname" type="java.lang.String">
                <column name="lastname" length="45" />
            </key-property>
        </composite-id>
        <property name="age" type="java.lang.Integer">
            <column name="age" not-null="true" />
        </property>
    </class>
</hibernate-mapping>

Hibernate配置文件
<hibernate-configuration>
     <session-factory>
    <property name="connection.username">root</property>
    <property name="connection.url">
        jdbc:mysql://localhost:3306/sample
    </property>
    <property name="dialect">
        org.hibernate.dialect.MySQLDialect
    </property>
    <property name="connection.password">root</property>
    <property name="connection.driver_class">
        com.mysql.jdbc.Driver
    </property>
    <property name="show_sql">true</property>
    <mapping resource="fire/User3.hbm.xml" />
     </session-factory>
</hibernate-configuration>

測試類
插入數據:
User3Id userid=new User3Id();
userid.setFirstname("a");
userid.setLastname("b");
User3 user=new User3();
user.setId(userid);
user.setAge(20);
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
s.save(user);
t.commit();
HibernateSessionFactory.closeSession();
查詢數據:
User3Id userid=new User3Id();
userid.setFirstname("a");
userid.setLastname("b");
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
User3 user=(User3) s.load(User3.class,userid);
System.out.println(user.getAge());
t.commit();
HibernateSessionFactory.closeSession();


Blob、Clob字段的映射
在許多情況下,我們需要在庫表中保存大型字符串,或者二進制數據。Hibernate中也提供了對Blob、Clob類型的內置支持。
Blob和Clob字段的區別在於,Blob字段採用字節存儲,適合保存二進制數據,如圖片文件,Clob字段採用多字節存儲,適合保存大型文本數據。
對於user1表而言,假設我們需要爲用戶增加兩個大型字段,其image字段用於保存照片(Blob),resume字段用於保存簡歷(Clob)。
經過改造的user4表結構如下:
CREATE TABLE  `sample`.`user4` (
  `id` varchar(32) NOT NULL default '',
  `name` varchar(45) NOT NULL default '',
  `image` blob,
  `resume` text,
  PRIMARY KEY  (`id`)
)
舉例:
實體類
package fire;

import java.sql.Blob;
import java.sql.Clob;

public class User4 implements java.io.Serializable {
    private String id;
    private String name;
    private Blob image;
    private Clob resume;
    ……
}

Hibernate映射文件
<hibernate-mapping>
  <class name="fire.User4" table="user4" catalog="sample">
    <id name="id" type="java.lang.String">
      <column name="id" length="32" />
      <generator class="uuid.hex" />
    </id>
    <property name="name" type="java.lang.String">
      <column name="name" length="45" not-null="true" />
    </property>
    <property name="image" type="java.sql.Blob" column="image">
    </property>
    <property name="resume" type="java.sql.Clob" column="resume">
    </property>
  </class>
</hibernate-mapping>

Hibernate配置文件
<hibernate-configuration>
     <session-factory>
    <property name="connection.username">root</property>
    <property name="connection.url">
        jdbc:mysql://localhost:3306/sample
    </property>
    <property name="dialect">
        org.hibernate.dialect.MySQLDialect
    </property>
    <property name="connection.password">root</property>
    <property name="connection.driver_class">
        com.mysql.jdbc.Driver
    </property>
    <property name="show_sql">true</property>
    <mapping resource="fire/User4.hbm.xml" />
     </session-factory>
</hibernate-configuration>

測試類
插入數據:
User4 user = new User4();
user.setName("zs");
FileInputStream imgis = new FileInputStream("a.jpg");
Blob img = Hibernate.createBlob(imgis);
user.setImage(img);
Clob resume = Hibernate.createClob("This is Clob");
user.setResume(resume);
Session s = HibernateSessionFactory.getSession();
Transaction t = s.beginTransaction();
s.save(user);
t.commit();
HibernateSessionFactory.closeSession();
查詢數據:
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
User4 user=(User4) s.load(User4.class,"297e421018bd28a80118bd28aac30001");
Clob resume=user.getResume();
//通過Clob.getSubString()方法獲取Clob字段內容
System.out.println(resume.getSubString(1,(int)resume.length()));
Blob img=user.getImage();
//通過Blob.getBinaryStream()方法獲取二進制流
InputStream is=img.getBinaryStream();
FileOutputStream fos=new FileOutputStream("b.jpg");
byte[] buf=new byte[102400];int len;
while((len=is.read(buf))!=-1){
    fos.write(buf,0,len);
}
fos.close();is.close();t.commit();
HibernateSessionFactory.closeSession();

數據關聯
在前面的內容中,我們討論了基於Hibernate的實體映射技術及其設計上的一些通用策略。對於ORM而言,另一個非常關鍵的特性,就是對實體之間關聯關係的管理。
數據關聯是ORM的一個重要特徵,但往往也是導致系統性能低下的原因,不良的關聯設計會對系統的性能表現產生致命的影響,在實際開發中我們需要特別注意這一點。

常用的數據關聯:
一對一併聯
一對多並聯

一對一關聯
一個典型的一對一關聯的實例:中國公民只允許擁有一份護照,這裏我們把用戶[user5]和護照[passport]設定爲一對一關係:
CREATE TABLE  `sample`.`user5` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(45) NOT NULL default '',
  `age` int(11) NOT NULL default '0',
  PRIMARY KEY  (`id`)
)
CREATE TABLE  `sample`.`passport` (
  `id` int(11) NOT NULL default '0',
  `serial` varchar(30) NOT NULL default '',
  PRIMARY KEY  (`id`),
  CONSTRAINT `FK_passport` FOREIGN KEY (`id`) REFERENCES `user5` (`id`)
)
舉例:
實體類
User5.java
package fire;

public class User5 implements java.io.Serializable {
    private Integer id;
    private String name;
    private Integer age;
    private Passport passport;
    ……
    public Passport getPassport() {
        return passport;
    }
    public void setPassport(Passport passport) {
        this.passport = passport;
    }
}

Passport.java
package fire;

public class Passport implements java.io.Serializable {
    private Integer id;
    private String serial;
    private User5 user;
    ……
    public User5 getUser() {
        return user;
    }
    public void setUser(User5 user) {
        this.user = user;
    }
}

Hibernate映射文件
User5.hbm.xml
<hibernate-mapping>
  <class name="fire.User5" table="user5" catalog="sample">
    <id name="id" type="java.lang.Integer">
      <column name="id" />
      <!-- 自動採用數據庫的主鍵生成方式 -->
      <generator class="native" />
    </id>
    <property name="name" type="java.lang.String">
      <column name="name" length="45" not-null="true" />
    </property>
    <property name="age" type="java.lang.Integer">
      <column name="age" not-null="true" />
    </property>
    <!-- 1.通過one-to-one節點,將User5類與Passport類相關聯
           2.級聯關係設置爲all,它指的是關聯對象是否同步執行同一操作 -->
    <one-to-one name="passport" class="fire.Passport" cascade="all" />
  </class>
</hibernate-mapping>
Passport.hbm.xml
<hibernate-mapping>
  <class name="fire.Passport" table="passport" catalog="sample">
    <id name="id" type="java.lang.Integer">
      <column name="id" />
      <!-- 通過foreign類型的主鍵生成器與外鍵共享主鍵值 -->
      <generator class="foreign">
        <param name="property">user</param>
      </generator>
    </id>
    <property name="serial" type="java.lang.String">
      <column name="serial" length="30" not-null="true" />
    </property>
    <!-- constrained屬性必須設定爲true,以告知Hibernate當前表主鍵上存在一個約束:passport表引用了user5表的主鍵 -->
    <one-to-one name="user" class="fire.User5" constrained="true" />
  </class>
</hibernate-mapping>

Hibernate配置文件
<hibernate-configuration>
     <session-factory>
    <property name="connection.username">root</property>
    <property name="connection.url">
        jdbc:mysql://localhost:3306/sample
    </property>
    <property name="dialect">
        org.hibernate.dialect.MySQLDialect
    </property>
    <property name="connection.password">root</property>
    <property name="connection.driver_class">
        com.mysql.jdbc.Driver
    </property>
    <property name="show_sql">true</property>
    <mapping resource="fire/User5.hbm.xml" />
    <mapping resource="fire/Passport.hbm.xml" />
     </session-factory>
</hibernate-configuration>

測試類
插入數據:
User5 user=new User5();
user.setName("zs");
user.setAge(new Integer(20));
Passport passport =new Passport();
passport.setSerial("654321");
passport.setUser(user);
user.setPassport(passport);
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
s.save(user);
t.commit();
HibernateSessionFactory.closeSession();
查詢數據:
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
User5 user=(User5) s.get(User5.class,new Integer(1));
System.out.println(user.getName());
System.out.println(user.getPassport().getSerial());
t.commit();
HibernateSessionFactory.closeSession();

一對多關聯
一對多關聯在系統實現中非常常見,在我們現在的這個示例中,每個用戶[user6]都關聯到多個地址[address],如一個用戶可能擁有辦公室地址、家庭地址等多個地址屬性。這樣,在系統中,就反映爲一個“一對多”關聯:
CREATE TABLE  `sample`.`user6` (
  `id` varchar(32) NOT NULL default '',
  `name` varchar(45) NOT NULL default '',
  `age` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (`id`)
)
CREATE TABLE  `sample`.`address` (
  `id` varchar(32) NOT NULL default '',
  `address` varchar(45) NOT NULL default '',
  `tel` varchar(45) NOT NULL default '',
  `user_id` varchar(32) NOT NULL default '',
  PRIMARY KEY  (`id`),
  CONSTRAINT `FK_addr` FOREIGN KEY (`user_id`) REFERENCES `user6` (`id`)
)
實體類
User6.java
package fire;

import java.util.HashSet;
import java.util.Set;
public class User6 implements java.io.Serializable {
    private String id;
    private String name;
    private Integer age;
    private Set addresses = new HashSet(0);
    ……
    public Set getAddresses() {
        return this.addresses;
    }
    public void setAddresses(Set addresses) {
        this.addresses = addresses;
    }
}

Address.java
package fire;

public class Address implements java.io.Serializable {
    private String id;
    private User6 user6;
    private String address;
    private String tel;
    ……
    public User6 getUser6() {
        return this.user6;
    }
    public void setUser6(User6 user6) {
        this.user6 = user6;
    }
}

Hibernate映射文件
User6.hbm.xml
<hibernate-mapping>
  <class name="fire.User6" table="user6" catalog="sample">
    <id name="id" type="java.lang.String">
      <column name="id" length="32" />
      <generator class="uuid.hex" />
    </id>
    <property name="name" type="java.lang.String">
      <column name="name" length="45" not-null="true" />
    </property>
    <property name="age" type="java.lang.Integer">
    <column name="age" not-null="true" />
    </property>
    <set name="addresses" inverse="true" cascade="all">
      <key><column name="user_id" length="32" not-null="true" /></key>
      <one-to-many class="fire.Address" />
    </set>
  </class>
注意:(</hibernate-mapping>
對於one-to-many關聯關係,我們可以採用java.util.set類型的集合,表現在XML映射文件中也就是<set>…</set>節點。)
(這裏inverse被設爲“true”,意味着User6不再作爲主控方,而是將關聯關係的維護工作交給關聯對象Address來完成。)
(這樣Address對象在持久化過程中,就可以主動獲取其關聯的User6對象的id,並將其作爲自己的user_id,之後執行一次insert操作即可完成全部工作。)

Address.hbm.xml
<hibernate-mapping>
  <class name="fire.Address" table="address" catalog="sample">
    <id name="id" type="java.lang.String">
      <column name="id" length="32" />
      <generator class="uuid.hex" />
    </id>
    <many-to-one name="user6" class="fire.User6" fetch="select">
      <column name="user_id" length="32" not-null="true" />
    </many-to-one>
    <property name="address" type="java.lang.String">
      <column name="address" length="45" not-null="true" />
    </property>
    <property name="tel" type="java.lang.String">
      <column name="tel" length="45" not-null="true" />
    </property>
  </class>
</hibernate-mapping>

Hibernate配置文件
<hibernate-configuration>
     <session-factory>
    <property name="connection.username">root</property>
    <property name="connection.url">
        jdbc:mysql://localhost:3306/sample
    </property>
    <property name="dialect">
        org.hibernate.dialect.MySQLDialect
    </property>
    <property name="connection.password">root</property>
    <property name="connection.driver_class">
        com.mysql.jdbc.Driver
    </property>
    <property name="show_sql">true</property>
    <mapping resource="fire/User6.hbm.xml" />
    <mapping resource="fire/Address.hbm.xml" />
     </session-factory>
</hibernate-configuration>

測試類
插入數據:
User6 user = new User6();
user.setName("zs");
user.setAge(new Integer(20));
Address addr1 = new Address();
addr1.setAddress("HongKong");
addr1.setTel("123456");
addr1.setUser6(user);
Address addr2 = new Address();
addr2.setAddress("ShangHai");
addr2.setTel("654987");
addr2.setUser6(user);
user.getAddresses().add(addr1);
user.getAddresses().add(addr2);
Session s = HibernateSessionFactory.getSession();
Transaction t = s.beginTransaction();
s.save(user);
t.commit();
HibernateSessionFactory.closeSession();
查詢數據:
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
User6 user=(User6) s.load(User6.class,"297e421018c1a9bd0118c1a9bf7b0001");
System.out.println(user.getName());
Iterator it=user.getAddresses().iterator();
while(it.hasNext()){
    Address addr=(Address) it.next();
    System.out.println(addr.getAddress());
}
t.commit();
HibernateSessionFactory.closeSession();

Hibernate數據檢索

Criteria Query

Criteria概述
Criteria Query通過面向對象的設計,將數據查詢條件封裝爲一個對象。簡單來講,Criteria Query可以看作是傳統SQL的對象化表示,如:
Criteria criteria=session.createCriteria(User.class);
criteria.add(Restrictions.eq("name","zs"));
criteria.add(Restrictions.eq("sex","男"));
Hibernate在運行期會根據Criteria中指定的查詢條件(也就是上面代碼中通過criteria.add方法添加的查詢表達式)生成相應的SQL語句。
select * from user where name='zs' and sex='男'
這種方式的特點是比較符合Java程序員的編碼習慣,並且具備清晰的可讀性。正因爲此,不少ORM實現中都提供了類似的實現機制。

Criteria查詢表達式
Criteria本身只是一個查詢容器,具體的查詢條件需要通過Criteria.add方法添加到Criteria實例中。
如前例所示,Restrictions對象具體描述了查詢條件。針對SQL語法,它提供了對應的查詢限定機制。
        方法            描述
Restrictions.eq    對應SQL“field = value”表達式
如Expression.eq("name","zs")
Restrictions.allEq    參數爲一個Map對象,其中包含了多個屬性-值對應關係。相當於多個Expression.eq關係的疊加
Restrictions.gt        對應SQL中的 “field > value” 表達式
Restrictions.ge        對應SQL中的 “field >= value” 表達式
Restrictions.lt        對應SQL中的 “field < value” 表達式
Restrictions.le        對應SQL中的 “field <= value” 表達式
Restrictions.between    對應SQL中的 “between” 表達式
Restrictions.like        對應SQL中的 “field like value” 表達式
Restrictions.in        對應SQL中的 ”field in …” 表達式
Restrictions.eqProperty    對應SQL中的 “field = field” 表達式
Restrictions.gtProperty    對應SQL中的 “field > field” 表達式
Restrictions.geProperty    對應SQL中的 “field >= field” 表達式
Restrictions.ltProperty    對應SQL中的 “field < field” 表達式
Restrictions.leProperty    對應SQL中的 “field <= field” 表達式
Restrictions.and        and關係組合
Restrictions.or        or關係組合
Restrictions.not        not關係組合
注意:Restrictions條件方法中的屬性名參數對應實體類的屬性名,而非庫表中的實際字段名稱。
Criteria查詢表達式示例
查詢名爲“zs”的用戶記錄:
Session s = HibernateSessionFactory.getSession();
Criteria criteria = s.createCriteria(User6.class);
criteria.add(Restrictions.eq("name", "zs"));
List list = criteria.list();
for (int i = 0; i < list.size(); i++) {
    User6 user = (User6) list.get(i);
    System.out.println(user.getName());
}
HibernateSessionFactory.closeSession();
查詢所有年齡大於18歲的用戶記錄:
Criteria criteria = s.createCriteria(User6.class);
criteria.add(Restrictions.gt("age",new Integer(18)));
查詢所有年齡大於20歲且小於30的用戶記錄:
Criteria criteria = s.createCriteria(User6.class);
criteria.add(Restrictions.between("age", 20, 30));

Criteria示例查詢
Example類實現了Criterion接口,同樣,它也可以用作Criteria的查詢條件。Example的作用是:根據已有對象,查找屬性之相符的其它對象。
Session s = HibernateSessionFactory.getSession();
Criteria criteria = s.createCriteria(User6.class);
User6 exampleuser = new User6();
exampleuser.setName("zs");
criteria.add(Example.create(exampleuser));
List list = criteria.list();
for (int i = 0; i < list.size(); i++) {
    User6 user = (User6) list.get(i);
    System.out.println(user.getName());
}
HibernateSessionFactory.closeSession();
這裏我們新建了一個User6對象exampleUser,並作爲範本,查詢所有name屬性與之相同的用戶記錄。

Criteria複合查詢
我們需要查找出所有位於上海的用戶,通過Criteria的複合查詢我們可以輕鬆完成這個任務。
Session s=HibernateSessionFactory.getSession();   
Criteria criteria=s.createCriteria(User6.class);
Criteria addrcriteria=criteria.createCriteria("addresses");
addrcriteria.add(Restrictions.eq("address","shanghai"));
List list=criteria.list();
for(int i=0;i<list.size();i++){
    User6 user=(User6)list.get(i);
    System.out.println(user.getName());
}
HibernateSessionFactory.closeSession();
可以看到,我們通過Criteria.createCriteria方法在原有的Criteria對象的基礎上構建複合查詢。


Criteria高級特性
限定返回的記錄範圍
Session s = HibernateSessionFactory.getSession();
Criteria criteria = s.createCriteria(User6.class);
//設置獲取第一個記錄的位置
criteria.setFirstResult(0);
//設置獲取記錄的最大數量
criteria.setMaxResults(10);
List list = criteria.list();
for (int i = 0; i < list.size(); i++) {
    User6 user = (User6) list.get(i);
    System.out.println(user.getName());
}
HibernateSessionFactory.closeSession();

記錄排序
Session s = HibernateSessionFactory.getSession();
Criteria criteria = s.createCriteria(User6.class);
//設置結果集的排序規則
criteria.addOrder(Order.desc("age"));
List list = criteria.list();
for (int i = 0; i < list.size(); i++) {
    User6 user = (User6) list.get(i);
    System.out.println(user.getName());
}
HibernateSessionFactory.closeSession();

Order類
Session s=HibernateSessionFactory.getSession();
Criteria criteria=s.createCriteria(User6.class);
//設置參加分組的實體類屬性
criteria.setProjection(Projections.groupProperty("age"));
Iterator it=criteria.list().iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
HibernateSessionFactory.closeSession();

Projections類
groupProperty()
avg()
sum()
min()
max()
count()

分組查詢
Session s=HibernateSessionFactory.getSession();
Criteria criteria=s.createCriteria(User6.class);
//計算某個實體類屬性的平均值
criteria.setProjection(Projections.avg("age"));
Iterator it=criteria.list().iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
HibernateSessionFactory.closeSession();

統計查詢
Session s=HibernateSessionFactory.getSession();
Criteria criteria=s.createCriteria(User6.class);
//計算某個實體類屬性的平均值
criteria.setProjection(Projections.avg("age"));
Iterator it=criteria.list().iterator();
while(it.hasNext()){
    System.out.println(it.next());
}
HibernateSessionFactory.closeSession();

分類彙總
//實例化分組統計列表
ProjectionList projectionList=Projections.projectionList();
//向列表中添加分組條件
projectionList.add(Projections.groupProperty("age"));
//向列表中添加統計條件
projectionList.add(Projections.rowCount());
Session s=HibernateSessionFactory.getSession();
Criteria criteria=s.createCriteria(User6.class);
//設置分組統計
criteria.setProjection(projectionList);
Iterator it=criteria.list().iterator();
while(it.hasNext()){
    Object[] o=(Object[]) it.next();
    System.out.println(o[0]+"\t"+o[1]);
}
HibernateSessionFactory.closeSession();

Criteria綜述
Criteria作爲一種對象化的查詢封裝模式非常適合程序員的口味,簡單易用,清晰明瞭。不過由於Hibernate在實現過程中更加集中在HQL查詢語言上,因此Criteria的功能實現還沒做到盡善盡美,因此,在實際開發中最常用的還是Hibernate官方推薦的查詢封裝模式:HQL。
這裏需要說明的是,HQL和Criteria並不是非此即彼、相互孤立的技術,這兩者相輔相成,在系統開發中,可以通過適當搭配使用這兩種技術以獲得最有效、最便捷的功能實現方式。


Hibernate Query Language[HQL]
Criteria提供了符合面對象編程風格的查詢封裝模式。不過,HQL提供了更加豐富靈活的特性,它在含蓋了Criteria功能範圍的前提下,提供了更爲強大的查詢能力,因此,在Hibernate官方開發手冊中,也將HQL作爲推薦的查詢模式。
相對Criteria,HQL提供了更接近傳統SQL語句的查詢語法,也提供了更全面的特性。完整的HQL語法結構如下:
[select/update/delete…] [from…] [where…]
[group by… [having…]] [order by…]
可以看到,HQL的語法與SQL非常類似。HQL基於SQL,同時也提供了更加面向對象的封裝。
鑑於HQL在Hibernate實體操作中的重要地位,下面我們將另起一節,專門圍繞HQL的具體應用技術進行探討。

HQL實用技術

實體查詢
在之前的內容中,我們已經多次涉及HQL的相關內容。下面我們來看最簡單的例子:
Session s=HibernateSessionFactory.getSession();
Query q=s.createQuery("from User6");
List list=q.list();
for(int i=0;i<list.size();i++){
    User6 user=(User6) list.get(i);
    System.out.println(user.getName());
}
HibernateSessionFactory.closeSession();
上面的hql:“from User6”,對應的SQL爲“select * from user6”。
提示:HQL子句本身大小無關,但是其中出現的類名和屬性名必須注意大小寫區分。
where子句
如果我們需要取出名爲“zs”的用戶記錄,類似SQL,我們可以通過HQL語句加以限定:
String hql="from User6 as user where user.name='zs'";
Query query=session.createQuery(hql);
List list=query.list();
這裏我們新引入了兩個子句“as”和“where”,as子句爲類名創建了一個別名,而where子句指定了限定條件。其中as子句可忽略,如:
String hql="from User6 user where user.name='zs'";
Query query=session.createQuery(hql);
List list=query.list();
在where子句中,我們可以看出通過比較操作符指定篩選條件,如:
=,!=,<,>,>=,<=,between,in,is,like
where子句示例:
from User6 user where user.age>20
from User6 user where user.age between 20 and 30
from User6 user where user.age in(18,28)
from User6 user where user.name is null
from User6 user where user.name like '張%'
from User6 user where (user.age % 2=1)
from User6 user where (user.age>20) and (user.name like '張%')

屬性查詢
有時我們並不需要獲取完整的實體對象,如在一個下拉框中顯示用戶名,此時我門需要的數據可能僅僅是實體對象的某個屬性(庫表記錄中的某個字段信息)。同樣,通過HQL我們也可以簡單地做到這一點:
Query query=s.createQuery("select user.name from User6 user");
List list=query.list();
for(int i=0;i<list.size();i++){
    System.out.println(list.get(i));
}
HQL “select user.name from User6 user”指定了我們只需獲取User6對象的name屬性(也就是user6表的name字段)。此時返回的list數據結構中,每個條目都是一個String類型的name數據(而非User6對象)。

我們也可以通過一條HQL獲取多個屬性:
Query query=s.createQuery("select name,age from User6");
List list=query.list();
for(int i=0;i<list.size();i++){
    Object[] o=(Object[]) list.get(i);
    System.out.println(o[0]);
    System.out.println(o[1]);
}
“select user.name,user.age from User6 user”表明我們需要讀取name和age屬性的內容。而此時,返回的list數據結構中,每個條目都是一個對象數組(Object[]),其中依次包含了我們所獲取的屬性數據。

如果覺得返回數組的方式不夠符合面向對象的風格,我們可以通過在HQL中動態構造對象實例的方法對這些平面化的數據進行封裝。
Query query=s.createQuery("select new User6(name,age) from User6");
List list=query.list();
for(int i=0;i<list.size();i++){
    User6 user=(User6) list.get(i);
    System.out.println(user.getName());
    System.out.println(user.getAge());
}
上面,我們實現了通過HQL獲取數據的部分屬性值,與此同時,我們也可以在HQL的Select子句中使用統計函數:
Query query=s.createQuery("select count(*),min(age) from User6");
List list=query.list();
for(int i=0;i<list.size();i++){
    Object[] o=(Object[]) list.get(i);
    System.out.println(o[0]);
    System.out.println(o[1]);
}

實體更新與刪除
通過delete、 update子句,數據的刪除與更新操作可以以更加靈活的方式實現。
以下代碼將所有用戶的年齡屬性更新爲18:
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
Query query=s.createQuery("update User6 set age=18");
query.executeUpdate();
t.commit();
HibernateSessionFactory.closeSession();
以下代碼刪除了所有年齡大於18的用戶記錄:
Session s=HibernateSessionFactory.getSession();
Transaction t=s.beginTransaction();
Query query=s.createQuery("delete User6 where age>18");
query.executeUpdate();
t.commit();
HibernateSessionFactory.closeSession();


分組與排序
Order by子句
與SQL類似,HQL通過order by子句實現對查詢結果的排序,如:
from User6 user order by user.name
默認情況下按順序排序,我們可以通過指定排序策略進行調整:
from User6 user order by user.name desc
order by子句可指定多個排序條件:
from User6 user order by user.name,user.age desc

Group by子句
通過Group by子句可進行分組統計。如下例中, 我們通過Group by子句實現了同年齡用戶的統計:
Query query=s.createQuery("select age,count(*) from User6 group by age");
List list=query.list();
for(int i=0;i<list.size();i++){
    Object[] o=(Object[]) list.get(i);
    System.out.println(o[0]);
    System.out.println(o[1]);
}


Having子句(和Group by連用在分組後的數據在滿足條件)
在上面例子中,我們對同齡用戶進行了統計,獲得了每個年齡層次中的用戶數量,假設我們只對超過10個人的年齡組感興趣,該如何處理?
Having子句可以幫助我們簡單地實現這一功能:
Query query=s.createQuery("select age,count(*) from User6 group by age having count(*)>10");
List list=query.list();
for(int i=0;i<list.size();i++){
    Object[] o=(Object[]) list.get(i);
    System.out.println(o[0]);
    System.out.println(o[1]);
}

參數綁定
在上面的HQL示例中,查詢參數均直接在HQL中表達,如:
from User6 user where user.age>20
其中的“20”作爲查詢參數,直接寫入了HQL。如果要求查詢的年齡參數爲變量,那麼可以用以下方式進行HQL拼裝:
String hql="from User6 user where user.age>"+age;
這種表達方式雖然同樣能實現我們期望的功能,但卻存在着以下缺陷:
編碼更加凌亂,可讀性降低、難以進行性能優化、引入額外的安全風險
通過Query接口進行參數填充:
Query query=s.createQuery("from User6 where name=?");
query.setString(0,"zs");
上面我們介紹了順序佔位符的使用,除了順序佔位符之外,Hibernate還支持引用佔位符。如下:
Query query=s.createQuery("from User6 where name=:name");
query.setString("name","zs");
我們甚至還可以用一個JavaBena封裝查詢參數,如:
Query query=s.createQuery("from User6 where name=:name");
User6 user=new User6();
user.setName("zs");
query.setProperties(user);

引用查詢
我們可能遇到過如下編碼規範:“代碼中不允許出現SQL語句”。
代碼中是否出現SQL語句並不是我們這裏所要探討的主題。我們關心的是這條規則之後的制定意圖:SQL語句混雜在代碼之間將破壞代碼的可讀性,並使得系統的可維護性降低。爲了避免這樣的情況出現,我們通常採取將SQL配置化的方式,也就是說,將SQL保存在配置文件中,需要調用的時候再進行讀取。
Hibernate提供了HQL可配置化的內置支持。
我們可以在實體影射文件中,通過query節點定義查詢語句(與class節點同級):
<query name="queryByName">
    <![CDATA[from User6 where name=?]]>
</query>
之後,我們即可通過Session.getNamedQuery方法從配置文件中調用對應的HQL,如:
Query query=s.getNamedQuery("queryByName");
query.setString(0, "zs");
List list=query.list();


聯合查詢
我們知道,SQL總通過join子句實現多表之間的聯合查詢。HQL提供了以下幾種聯合查詢機制:
內連接:
Query query=s.createQuery("from User6 user inner join user.addresses");
List list=query.list();
for(int i=0;i<list.size();i++){
    Object[] o=(Object[]) list.get(i);
    System.out.println(o[0]);
    System.out.println(o[1]);
}
條件連接:
from User6 u,Address a where u.id=a.user6.id

子查詢
子查詢是SQL中非常重要的功能。它可以在SQL中利用另外一條SQL的查詢結果。
在之前的例子中,我們通過以下HQL獲取了所有用戶記錄:
from User6
假設我們需要從此查詢結果中提取居住在上海的User6對象,那麼我們可以編寫如下HQL:
from User6 where id in(select user6.id from Address where address='shanghai')

數據加載方式
在傳統JDBC操作中,我們通常通過SQL語句加載所需的數據進行處理,當SQL提交之後,這些數據就被讀取待用。
而在Hibernate世界裏,我們擁有了更多的選擇(針對關聯數據):
1.即時加載 <set lazy="false">
2.延遲加載<set lazy="true">
3.預先加載<one-to-one fetch="join">

SQL查詢
如果需要執行某此特殊的SQL語句,我們可以通過Session.connection()方法獲取JDBC Connection實例進行調用。
Session s=HibernateSessionFactory.getSession();
Connection conn=s.connection();
Statement stmt=conn.createStatement();
ResultSet rs=stmt.executeQuery("select * from user6");
while(rs.next()){
    System.out.println(rs.getString("name"));
}
rs.close();
stmt.close();
conn.close();
HibernateSessionFactory.closeSession();










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章