一、Hibernate 檢索
- hibernate 提供5種檢索數據的方式
導航對象圖檢索方式: 根據已經加載的對象導航到其他對象
Customer c = (Customer)session.get(Customer.class, 1); // 持久態對象
c.getOrders().size(); // c 對象關聯 order 集合 ,hibernate 會自動檢索 order數據
OID 檢索方式: 按照對象的 OID 來檢索對象
session.get / session.load
HQL 檢索方式: 使用面向對象的 HQL 查詢語言
session.createQuery(hql)
QBC 檢索方式: 使用 QBC(Query By Criteria) API 來檢索對象. 這種 API 封裝了基于字符串形式的查詢語句, 提供了更加面向對象的查詢接口.
session.createCriteria(Xxx.class);
本地 SQL 檢索方式: 使用本地數據庫的 SQL 查詢語句
session.createSQLQuery(sql)
2、 HQL 是 Hibernate 最常用檢索方式
支持 所有 SQL支持檢索方式
步驟 :
1) 獲得Session
2) 編寫HQL
3) 通過 session.createQuery(hql) 創建Query對象
4) 為Query對象 設置條件參數
5) 執行查詢 list() ---- 返回一個集合列表 、 uniqueResult();--- 返回一個查詢結果
- Qurey 接口支持方法鏈編程風格 , 將上面所有步驟寫入一句程序代碼中
3、 編寫測試用例,創建初始數據
創建4個Customer , 每個Customer 創建 10個 Order
4、 簡單查詢, hibernate 企業開發主流查詢 HQL 和 QBC
-
查詢所有數據
// HQL
String hql = "from Customer";
Query query = session.createQuery(hql);
List<Customer> list = query.list();
System.out.println(list);// QBC Criteria criteria = session.createCriteria(Customer.class); List<Customer> list2 = criteria.list(); System.out.println(list2);
HQL 和 QBC 都支持鏈式編程寫法
List<Customer> list3 = session.createQuery("from Customer").list();
5、 本地SQL 檢索
* 編寫及其復雜的查詢,企業內部大多使用 SQL 語句
// 內連接 寫法一 : select * from A inner join B on A.id = B.A_id;
// 內連接 寫法二 (隱式): select * from A,B where A.id = B.A_id ;
String sql = "select * from customers , orders where customers.id = orders.customer_id and customers.name = ?";
SQLQuery sqlQuery = session.createSQLQuery(sql);
// 設置參數
sqlQuery.setParameter(0, "mary");
List list = sqlQuery.list();
System.out.println(list);
* 當返回很多列時,默認將每條記錄保存在 Object[]中, 返回 List<Object[]>
* 將返回結果 與實體類 綁定,將每條數據 轉換實體類對象
String sql = "select orders.* from customers , orders where customers.id = orders.customer_id and customers.name = ?";
sqlQuery.addEntity(Order.class);
6、 編寫HQL時,通過as關鍵字 為類 起別名
from Customer as c where c.name=:custname
原來寫法: from Customer where name=:custname
使用別名時 ,as可以省略 from Customer c where c.name=:custname
- 別名主要使用 關聯復雜查詢時
7、 多態查詢
hibernate 檢索一個類 對應數據時, 將類所有子類(PO類) 對應數據表記錄返回
session.createQuery("from java.lang.Object").list();
- 將Object 所有子類 對應數據表的 數據查詢返回
from 關鍵字后面,如果PO類,省略包名, 如果不是PO類,必須寫完整包名類名
8、 查詢結果排序
// HQL
String hql = "from Customer order by name asc";
List list = session.createQuery(hql).list();
System.out.println(list);
// QBC
List list2 = session.createCriteria(Customer.class).addOrder(org.hibernate.criterion.Order.asc("name")).list();
System.out.println(list2);
9、 分頁查詢
Query 接口 和 Criteria 接口 都提供 setFirstResult 、setMaxResults 兩個方法,用于分頁查詢
* setFirstResult 起始記錄索引,第一條數據 索引 0
* setMaxResults 查詢返回記錄條數
案例:
// 分頁查詢,返回 25-34條訂單
// HQL
String hql = "from Order";
Query query = session.createQuery(hql);
// 設置分頁參數
query.setFirstResult(24); // 索引 是 起始記錄 -1
query.setMaxResults(10);
10、 檢索單一對象 query.uniqueResult() 、 criteria.uniqueResult()
該方法主要用于,只有1條數據結果
-
什么情況只有一條結果 : 用戶登錄、使用聚集函數 sum、count、avg、max、min
// 查詢mary的信息
Customer customer = (Customer) session.createQuery("from Customer where name = 'mary'").uniqueResult();
System.out.println(customer);// 使用聚集函數 -- 查詢客戶最大年齡
Integer age = (Integer) session.createQuery("select max(age) from Customer").uniqueResult();
System.out.println(age);
如果查詢結果 只有一條記錄 或者 無記錄,使用uniqueResult 是沒問題的, 但是如果查詢結果大于 一條記錄,報錯
org.hibernate.NonUniqueResultException: query did not return a unique result: 4
===============================================================================================================================
11、 帶有參數條件的查詢
1) 單表條件查詢
HQL寫法:
Customer customer1 = (Customer) session.createQuery("from Customer where name = ?").setParameter(0, "tom").uniqueResult();
Customer customer2 = (Customer) session.createQuery("from Customer where name = :cname").setParameter("cname", "tom").uniqueResult();
QBC寫法:
Customer customer3 = (Customer) session.createCriteria(Customer.class).add(Restrictions.eq("name", "tom")).uniqueResult();
* Restrictions 用來添加查詢條件 ,面向對象條件查詢
將參數綁定到一個持久化對象
Customer customer = new Customer();
customer.setId(1);
List list2 = session.createQuery("from Order where customer = ?").setEntity(0, customer).list(); // 通過customer_id 查詢
- 簡化為 List list = session.createQuery("from Order where customer.id = ?").setParameter(0, 1).list();
- setEntity 關聯對象 ,必須要有OID ,否則會報錯
使用QBC 為參數綁定 持久化對象
List list3 = session.createCriteria(Order.class).add(Restrictions.eq("customer", customer)).list(); // 通過customer_id 查詢
2) 多表條件查詢
hibernate HQL 支持7種 連接寫法
* (SQL標準)內連接 inner join 可以省略 inner,直接 join
* 迫切內連接 inner join fetch ------ 不是SQL寫法,是hibernate 提供
* 隱式內連接 不寫任何關鍵字,完成表關聯
* (SQL標準)左外連接 left outer join ,可以省略 outer ,直接 left join
* 迫切左外連接 left outer join fetch ----- 不是SQL語法
* (SQL標準)右外連接 right outer join
* (SQL標準)交叉連接 (笛卡爾積 ) ---------- 不講
問題: 區分內連接和迫切內連接, 左外連接和迫切左外連接
* 左外連接 List list = session.createQuery( "from Customer c left outer join c.orders").list(); 返回 List<Object[]>
每個數組兩個元素 ,一個Customer 一個Order
* 迫切左外連接 List list = session.createQuery("select distinct c from Customer c left outer join fetch c.orders").list();
返回 List<Customer> 保存所有Customer對象,需要distinct 排重重復
問題:多表關聯條件查詢,隱式內連接 和 QBC方式
// 隱式內連接 o.customer 當做Customer類數據表
List<Order> list = session.createQuery("from Order o where o.customer.name = ?").setParameter(0, "mary").list();
// QBC 連接查詢,必須使用 criteria.createAlias()
Criteria criteria = session.createCriteria(Order.class);
criteria.createAlias("customer", "c"); // 為訂單關聯Customer屬性起了別名
criteria.add(Restrictions.eq("c.name", "mary"));
List list2 = criteria.list();
* 在 createAlias 默認使用 inner join 內連接
criteria.createAlias("customer", "c", Criteria.LEFT_JOIN); 在關聯時使用左外連接
12 、投影查詢
查詢結果僅包含實體的部分屬性
* 查詢Customer的所有 name,age 屬性
HQL方式
session.createQuery("select name,age from Customer"); 返回 List<Object[]>
* 將結果保存Customer對象中,提供name和age 構造方法
session.createQuery("select new Customer(name,age) from Customer"); 返回 List<Customer>
可以將查詢結果 保存List 或者 Map集合
* select new list(name,age) from Customer
* select new map(name,age) from Customer
QBC方式 (開發中不用,非常麻煩)
List list3 = session
.createCriteria(Customer.class)
.setProjection(
Projections.projectionList()
.add(Property.forName("name"))
.add(Property.forName("age"))).list();
13、 分組統計查詢
count sum avg max min
* Long count = (Long) session.createQuery("select count() from Order").uniqueResult();
* List list2 = session.createQuery("select count() from Order group by customer").list();
14、 命名查詢語句
在開發中 hql語句 寫到代碼中,不方便維護, 將HQL定義到配置文件中 ------------ 命名查詢語句
在hbm映射文件 (也可以用注解配置)
<query name="findCustomerByName">
<![CDATA[from Customer where name = ?]]>
</query>
- 為hql語句 起了一個名字
程序代碼
Query query = session.getNamedQuery("findCustomerByName");
query.setParameter(0, "tom");
Customer customer = (Customer) query.uniqueResult();
15、 離線Criteria對象 --- DetachedCriteria
* 主要用于javaee分層開發,可以在web層封裝查詢條件,傳遞數據層 關聯Session進行查詢
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
detachedCriteria.add(Restrictions.eq("name", "kitty"));
// 傳遞數據層
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 將離線查詢對象 關聯到Session
Criteria criteria = detachedCriteria.getExecutableCriteria(session);
Customer customer = (Customer) criteria.uniqueResult();
HQL和QBC比較: 兩種查詢功能類似, 簡單查詢建議編寫HQL,對于復雜查詢,多條件組合查詢 建議編寫 QBC方式
============================================================================================================================
二、 hibernate 在進行數據檢索時,抓取策略 (Hibernate 優化環節)
1、 準備初始數據
4條Customer數據, 39條Order 數據
2、 區分立即檢索和延遲檢索
- 理解 為什么要使用 延遲檢索
get方法,采用策略 立即檢索
load方法,采用策略 延遲檢索
延遲加載在開發中,主要好處,延緩數據加載,在使用時才進行加載 (縮短數據在內存中時間)
3、 load方法 返回代理對象 (目標類 子類對象 )
- hibernate 返回代理對象 由 javassist-3.12.0.GA.jar 提供工具類 負責創建
Javassist是一個開源的分析、編輯和創建Java字節碼的類庫。
4、 延遲代理對象的數據初始化
方式一: 訪問代理對象 內部 除了 id屬性外 其它屬性 (導致延遲對象自動初始化 )
方式二: 調用 Hibernate.initialize(代理對象);
===========================================================
5、 區分類級別檢索和關聯級別的檢索
類級別檢索,通過session直接檢索 某一個類 對應數據表數據
session.load(Customer.class , 1) ; 類級別
session.createQuery("from Order"); 類級別
關聯級別檢索,程序內部已經獲得持久對象,通過對象引用關系,進行數據檢索
Customer c = session.load(Customer.class , 1) ; 類級別
c.getOrders().size() ; 關聯級別檢索
order.getCustomer().getName() ; 關聯級別檢索
6、 類級別檢索策略(抓取策略)
* 類級別可選的檢索策略包括立即檢索和延遲檢索, 默認為延遲檢索 (針對load方法 )
* 類級別的檢索策略可以通過 <class> 元素的 lazy 屬性進行設置
類級別檢索 get 、Query 默認使用立即檢索策略
load 默認使用延遲檢索, 在hbm文件 <class> 配置 lazy=false 使類級別檢索變為立即檢索
- lazy=false 后 load方法效果 和 get方法相同,使用 立即檢索
7、 關聯級別的檢索策略
c.getOrders().size() ; order.getCustomer().getName(); 屬于關聯級別的檢索
1) 多對多 和 一對多 情況下 <set> 元素中配置 抓取策略 ---- 關聯集合
<set> 元素提供 fetch 和 lazy 兩個屬性 決定檢索策略
* fetch 屬性 (select、subselect、join) ,主要決定SQL語句生成格式
* lazy 屬性 (false、true、extra)主要決定集合被初始化的時機
fetch 和 lazy 共有9種組合
fetch 屬性為 join, lazy屬性會被忽略, 生成SQL將采用迫切左外連接查詢 (left outer join fetch )
* SQL語句 左外連接,采用 立即檢索
fetch 屬性為 select ,將生成多條簡單SQL查詢
lazy = false 立即檢索
lazy = true 延遲檢索
lazy = extra 加強延遲檢索 (及其懶惰,比延遲更加延遲)
fetch 屬性為 subselect ,將生成子查詢的SQL語句
lazy = false 立即檢索
lazy = true 延遲檢索
lazy = extra 加強延遲檢索 (及其懶惰,比延遲更加延遲)
**** lazy=false 立即檢索,檢索類級別數據時,關聯級別數據也進行檢索
lazy=true 延遲檢索,檢索類級別數據時,不會檢索關聯級別的數據,用到了再進行檢索
lazy="extra" 及其懶惰,當程序第一次訪問 order 屬性的 size(), contains() 和 isEmpty() 方法時, Hibernate 不會初始化 orders 集合類的實例 ,例如 查詢size時,生成select count(*)
**** 因為fetch=join , session.get... 生成迫切左外連接查詢
使用Query對象查詢數據時,需要自己編寫hql語句, fetch=join 無效果,關聯集合將根據lazy 設置進行 加載
結論 : session.load/ session.get , fetch=join 生成迫切左外連接, lazy被忽略
session.createQuery(hql).list() 將忽略 fetch=join, lazy 將重新產生效果
2) 多對一 和 一對一 情況下 <many-to-one> 或者 <one-to-one> 配置抓取策略 ---- 關聯單一對象
<many-to-one> 元素也有一個 lazy 屬性和 fetch 屬性
fetch 決定SQL語句格式, lazy決定數據加載時間
fetch取值 join、 select
lazy取值 false、 proxy、no-proxy(不講解)
fetch 和 lazy 共有4種組合
fetch 屬性為 join, lazy屬性會被忽略, 生成SQL將采用迫切左外連接查詢 (left outer join fetch )
fetch 屬性為 select, 產生多條SQL
lazy=false 立即檢索
lazy=proxy 有關聯對象類級別檢索策略決定立即檢索 或者 延遲檢索
* 由Customer.hbm.xml <class name="cn.itcast.domain.Customer" lazy="??"> lazy決定立即檢索 還是 延遲檢索
**** Query的list 會忽略 fetch="join", lazy重新起作用
結論: 開發中能延遲都延遲,必須立即的 才立即的
=======================================================================================================================
8、 批量檢索的使用
批量檢索 可以解決 N+1 查詢問題
1) Customer 一方設置批量檢索
<set> 元素有一個 batch-size 屬性 ,設置批量檢索數量
N+1問題:查詢每個客戶訂單數,一次查詢所有客戶, 每個客戶訂單數產生單獨SQL查詢,如果有n個客戶,產生n條SQL
解決: Customer.hbm.xml 設置<set>元素 batch-size 一次查詢多個用戶訂單數
* <set name="orders" batch-size="3">
2) Order多方 設置批量檢索
查詢訂單和客戶,產生多余SQL
配置批量檢索,一次多查詢幾個客戶數據
解決: Customer.hbm.xml 在 <class name="cn.itcast.domain.Customer" batch-size="3">
三、 Hibernate 注解應用 (常用注解)
* hibernate 注解 簡化hbm 文件的配置
* 單表注解配置、 一對多、多對多注解配置
hibernate3.6 注解開發和xml開發 導入jar包 和 配置文件 是一樣的
1、 使用注解配置 PO對象
@Entity 實體類
@Table 生成目標表
@Id 主鍵
@GeneratedValue 主鍵生成策略
@Column 定義生成列
注解開發優先使用 javax.persistence.*
// Book 是一個實體類
@Entity
// 配置@Table 注解 ,設置生成表名
@Table(name = "book")
public class Book {
// 主鍵 @Id
@Id
// 生成策略 @GeneratedValue
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 普通屬性@Column 注解修飾
// 如果不寫@Column 列名就是 屬性名
@Column(name = "bookname", length = 20, unique = true, nullable = false)
private String name;
private Double price;
}
- @Id、@GeneratedValue(strategy = GenerationType.IDENTITY)、@Column(name = "bookname", length = 20, unique = true, nullable = false)
可以修改屬性,也可以放到屬性get方法上
在hibernate.cfg.xml 配置注解類
<mapping class="cn.itcast.domain.Book"/>
2、 主鍵生成策略
@GeneratedValue(strategy = GenerationType.IDENTITY) --- 只支持四種策略
編寫UUID 主鍵生成策略 Person
@Entity
@Table(name = "person")
public class Person {
@Id
@GenericGenerator(name = "myuuidgenerator", strategy = "uuid")
@GeneratedValue(generator = "myuuidgenerator")
private String id; // UUID
private String name;
}
3、 其它屬性
@Temporal 生成日期類型
@Transient 控制不生成
日期類型java.util.Date 對于MySQL 生成 datetime類,日期和時間都包括
* @Temporal(TemporalType.DATE) 數據表只保存日期
* @Temporal(TemporalType.TIME) 數據表只保存時間
* @Temporal(TemporalType.TIMESTAMP) 日期時間都保存
Hibernate 實體類中,所有get方法 屬性, 都會在數據表 自動生成列
* 有時屬性不想 生成列 @Transient 注解
=====================================================================================
4、 一對多
@OneToMany @ManyToOne
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
// targetEntity 類似 <one-to-many class="">
// mappedBy 作用 inverse=true
@OneToMany(targetEntity = Order.class, mappedBy = "customer")
@Cascade(value = { CascadeType.SAVE_UPDATE })
private Set<Order> orders = new HashSet<Order>();
}
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String address;
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "customer_id")
// 添加外鍵列
private Customer customer;
}
jar沖突錯誤 java.lang.NoSuchMethodError: javax.persistence.OneToMany.orphanRemoval()Z
* javaee.jar(myeclipse提供) 和 jpa的jar 沖突
* 解決 : 刪除javaee.jar 中 javax.persistence 包
- 級聯操作 @Cascade(value = { CascadeType.級別 })
DELETE_ORPHAN 已經過時 ,推薦使用 @OneToMany(orphanRemoval=true)
所有級聯級別
@OneToMany(targetEntity = Order.class, mappedBy = "customer", orphanRemoval = true)
@Cascade(value = { CascadeType.ALL })
private Set<Order> orders = new HashSet<Order>();
5、 多對多
@ManyToMany
- 注解配置多對多時,只需要一端配置中間表,另一端 mappedBy 放棄外鍵維護權
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String sname;
@ManyToMany(targetEntity = Course.class)
// 配置中間表
// joinColumns 當前類在中間表 外鍵列名
// inverseJoinColumns 對方類 在中間表 外鍵列名
@JoinTable(name = "student_course", joinColumns = { @JoinColumn(name = "student_id") }, inverseJoinColumns = { @JoinColumn(name = "course_id") })
private Set<Course> courses = new HashSet<Course>();
}
@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String cname;
@ManyToMany(targetEntity = Student.class, mappedBy = "courses")
// 放棄外鍵維護權
private Set<Student> students = new HashSet<Student>();
}
6、 抓取策略 (了解)
public class Customer {
// 抓取策略
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
private Set<Order> orders = new HashSet<Order>();
}
public class Order {
// 抓取策略
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.FALSE)
private Customer customer;
}
關聯級別抓取,配置@Fetch等價fetch屬性, 關聯集合 @LazyCollection , 關聯單一對象 @LazyToOne
7、 NamedQuery 命名查詢
@NamedQueries(value = { @NamedQuery(name = "findCustomerByName", query = "from Customer where name= ?") })
public class Customer {
...
}
程序代碼:
Query query = session.getNamedQuery("findCustomerByName");
query.setParameter(0, "張三");
Customer customer = (Customer) query.uniqueResult();