Hibernate3 第三天 html
第一天:三個準備、七個步驟java
次日:一級緩存、快照、多對多和一對多的配置mysql
學習內容:程序員
1)條件查詢分類(對象導航檢索)。web
2)HQL\SQL\QBC的各類查詢(基礎查詢、條件查詢、排序、分頁、投影查詢、統計分組、命名查詢、離線查詢等)。算法
1)延遲抓取和當即抓取策略 sql
類級別的抓取策略 數據庫
關聯集合級別的抓取策略 編程
2)批量抓取策略 數組
學習目標:
建立Hibernate項目:
構建Hibernate環境:導入jar包、hibernate.cfg.xml、log4j.properties、util工具類。
建立包:cn.itcast.a_onetomany,配置一對多的實體類和hbm映射文件的編寫:
實體類(Customer):
package cn.itcast.a_onetomany;
import java.util.HashSet; import java.util.Set;
public class Customer {
private Integer id; private String name; private String city;
//集合 //set:無需不重複 //也能夠用list:有序重複 //配置hbm.xml的時候,若是類中用的是list集合的話,那邊hbm中也可使用<bag>標籤配置集合 //<bag>:有序不重複,可是效率低下 private Set<Order> orders = new HashSet<Order>();
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public Set<Order> getOrders() { return orders; }
public void setOrders(Set<Order> orders) { this.orders = orders; }
@Override public String toString() { return "Customer [id=" + id + ", name=" + name + ", city=" + city + "]"; } } |
實體類(Order):
package cn.itcast.a_onetomany;
public class Order { private Integer id; private String name; private Double price;
private Customer customer ;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
@Override public String toString() { return "Order [id=" + id + ", name=" + name + ", price=" + price + "]"; } } |
hbm映射文件:
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置java類與表之間的對應關係 --> <!-- name:類名:類對應的完整的包路徑 table:表名 --> <class name="cn.itcast.a_onetomany.Customer" table="t_customer"> <!-- 主鍵 --> <id name="id"> <generator class="native"></generator> </id> <property name="name"></property> <property name="city"></property>
<set name="orders"> <key column="cid"></key> <one-to-many class="cn.itcast.a_onetomany.Order"/> </set>
</class> </hibernate-mapping>
|
Order.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置java類與表之間的對應關係 --> <!-- name:類名:類對應的完整的包路徑 table:表名 --> <class name="cn.itcast.a_onetomany.Order" table="t_order"> <id name="id"> <generator class="native"></generator> </id> <property name="name"></property> <property name="price"></property>
<many-to-one name="customer" class="cn.itcast.a_onetomany.Customer" column="cid"></many-to-one> </class> </hibernate-mapping>
|
核心配置文件中引入HBM映射配置:
<!-- 在覈心配置文件中 引用 mapping 映射文件 --> <mapping resource="cn/itcast/a_onetomany/Customer.hbm.xml"/> <mapping resource="cn/itcast/a_onetomany/Order.hbm.xml"/> |
建表測試是否配置成功:
@Test public void createTable(){ HibernateUtils.getSessionFactory(); } |
批量插入3個客戶和相應的訂單(共30個)
@Test @Test public void prepareData() { Session session = HibernateUtils.openSession(); session.beginTransaction();
//一個客戶對應多個訂單,一個客戶對應10個訂單 Customer customer = new Customer(); customer.setName("jack"); customer.setCity("北京");
session.save(customer);
for(int i=1;i<=10;i++) { Order o = new Order(); o.setName(customer.getName()+"的訂單"+i); o.setPrice(i*10d);
o.setCustomer(customer); session.save(o); }
session.getTransaction().commit(); session.close(); } |
【擴展】
問題:若是你在大批量的插入數據的時候,可能會報內存溢出的錯誤!
緣由:當save操做的時候,會將瞬時態轉換爲持久態,對象都放在了session的一級緩存中,若是超大量的數據,會撐爆一級緩存,致使內存溢出。
解決方案:
// 批插入的對象當即寫入數據庫並釋放內存
if(i%10000==0){ //刷出到數據庫 session.flush(); //清空一級緩存,釋放內存 session.clear(); } |
【提示:】
若是真的有大批量(幾十萬,上百萬,上千萬)的操做,其實,不太建議用hibernate,直接用jdbc(stmt. executeBatch())
Hibernate是經過檢索對象來查詢數據的,下面咱們瞭解一下,Hibernate提供的幾種檢索對象的方式:
徹底不須要懂HQL或者SQL,徹底的面向對象的操做方式
其中,前兩種屬於快捷檢索方式,比較簡單且經常使用,當這兩種檢索方式不能知足須要的時候,就須要使用後面幾種檢索方式,來自定義檢索,如複雜檢索條件等等。
後面三種是可代替的,
什麼是對象導航檢索?
當兩個對象之間配置了一對1、一對多或者多對多的關係的時候,能夠經過一個對象關聯獲取到另一個對象的檢索方式
如:Customer和Order對象的對象導航檢索:
【示例】建立cn.itcast.b_query,建立類TestQuery,而後進行以下的測試
1).查詢某客戶信息,而且打印其下全部的訂單;
2).查詢某訂單的信息,並打印其所屬客戶的信息。
@Test public void testNavigate(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //1).查詢某客戶信息,而且打印其下訂單; // Customer customer = (Customer) session.get(Customer.class, 1); // System.out.println(customer); //遍歷全部的訂單 // for(Order o:customer.getOrders()) // { // System.out.println(o); // }
//2).查詢某訂單的信息,並打印其所屬客戶的信息。 Order order = (Order) session.get(Order.class, 21); System.out.println(order); System.out.println(order.getCustomer());
session.getTransaction().commit(); session.close(); }
|
【提示】
在Hibernate的多表開發中,幾乎全部的關聯均可以進行雙向導航。
【提示2】
報錯的緣由:在customer的toString 方法中打印了orders集合,在order的toString方法中打印了customer,因爲會進行導航,因此致使內存溢出
因此改進:
【注意】
導航檢索必須是持久態對象,不然不能導航!
【導航檢索的概念擴展】
導航檢索就是在查詢出某個的PO對象(持久態)後,再訪問其關聯的集合對象屬性的時候,會自動發出SQL,來填充關聯屬性的所引用的對象。
如:查詢客戶後,再訪問其訂單屬性的時候,Hibernate會自動發出查詢訂單的語句,並自動填充訂單的值。
【注意】
默認狀況下,關聯屬性是延遲加載的,只有在訪問其關聯屬性的時候才發出SQL從數據庫查詢,致使查詢兩張表數據時,至少發出兩部分SQL語句,一個是主對象查詢語句,一個是關聯屬性的語句。
HQL支持各類各樣的常見查詢,和sql語言有點類似,它是Hibernate中使用最普遍的一種檢索方式。
QBC也支持HQL所支持的查詢方式,但徹底採用面向對象的思想來編程。完整使用詳見:
【提示瞭解】
HQL\QBC和SQL的區別?
HQL\QBC面向類和屬性,由hibernate自動生成sql語句,效率低
SQL面向表和字段,你寫啥語句,就運行啥語句,不會去組建sql語句,效率高
下面的課程將着重分別研究HQL、SQL、QBC這三種檢索方式。
【三種方式的選擇】
其中HQL和QBC是Hibernate推薦的檢索方式,但性能上會有折扣,而SQL的檢索方式雖然效率很高,但不是面向對象的,開發上麻煩一些。
【示例】
查詢出全部客戶信息。
@Test public void testQueryAll(){ //查詢出全部客戶信息 Session session = HibernateUtils.openSession(); session.beginTransaction();
//HQL方式一 // List<Customer> list = session.createQuery("from Customer").list(); //HQL方式二:給對象起別名的方式查詢 // List<Customer> list = session.createQuery("select c from Customer c").list(); //請注意:HQL不支持*的查詢方式的, // List<Customer> list = session.createQuery("select * from Customer").list();
//SQL //List<Customer> list = session.createSQLQuery("select * from t_customer").addEntity(Customer.class).list();
//QBC:徹底的面向對象 Criteria criteria = session.createCriteria(Customer.class); List<Customer> list = criteria.list();
System.out.println(list); session.getTransaction().commit(); session.close(); } |
【總結】
1.sql查詢的默認結果是List<Object[]>(用數組包裝了不少小customer),須要進行實體的綁定,SQLQuery提供了addEntity方法。
2.SQL的語句生成方式:
Hql和sql的方式:語句是可控的,能夠自定義的
Qbc:語句是徹底由hibernate本身生成。
HQL和SQL的查詢時的條件值能夠直接寫死,可是寫死的這種方式,幾乎不用,如今主要使用匿名參數(佔位符?)和命名參數這兩種主要方式進行參數注入。
【示例】
查詢姓名是rose的客戶,只返回rose的一條記錄。
//查詢姓名是rose的客戶,只返回rose的一條記錄。 @Test public void testQueryByCondition(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //方式一:寫死的方式,幾乎不用 // Customer customer = (Customer)session.createQuery("from Customer where name='rose'").uniqueResult(); //方式二:匿名方式:佔位符? // Customer customer = (Customer)session.createQuery("from Customer where name = ?") // .setParameter(0, "rose") // .uniqueResult(); //方式三: // Customer customer = (Customer)session.createQuery("from Customer where name = ?") // .setString(0, "rose") // .uniqueResult(); //方式四:命名的方式:注入參數 // Customer customer = (Customer)session.createQuery("from Customer where name = :name") //// .setString("name", "rose") // .setParameter("name", "rose") // .uniqueResult(); //sql //方式一:寫死 // Customer customer = (Customer)session.createSQLQuery("select * from t_customer where name ='rose'") // .addEntity(Customer.class)//使用sql,必定不能忘記封裝實體 // .uniqueResult(); //方式二:匿名方式:佔位符? // Customer customer = (Customer)session.createSQLQuery("select * from t_customer where name = ?") // .addEntity(Customer.class)//必須先封裝實體,再注入參數 //// .setString(0, "rose") // .setParameter(0, "rose") // .uniqueResult();
//方式三:命名方式: // Customer customer = (Customer)session.createSQLQuery("select * from t_customer where name = :name") // .addEntity(Customer.class) // .setString("name", "rose") //// .setParameter("name", "rose") // .uniqueResult();
//qbc Criteria criteria = session.createCriteria(Customer.class); //玩命的加條件 criteria.add(Restrictions.eq("name", "rose")); // /繼續加條件 criteria.add(Restrictions.like("city", "%上%")); //...繼續加條件
//當條件都加完以後, // List<Customer> list = criteria.list();//當結果是0/1條的時候,也可使用uniqueResult(),任何狀況之下,均可以使用list Customer customer = (Customer) criteria.uniqueResult();
System.out.println(customer); session.getTransaction().commit(); session.close();
} |
【HQL和QBC支持的各類運算和對應關係】:
【示例】
按照id對客戶信息進行排序。
//按照id對客戶信息進行排序。 @Test public void testQueryByOrder(){
Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql :都是面向對象 asc:升序 (默認值) desc:降序 // List<Customer> list = session.createQuery("from Customer order by id desc").list();
//sql // List<Customer> list = session.createSQLQuery("select * from t_customer order by id desc") // .addEntity(Customer.class) // .list();
//qbc List<Customer> list = session.createCriteria(Customer.class) .addOrder(org.hibernate.criterion.Order.desc("id"))//排序 org.hibernate.criterion.Order.desc("id") 降序 .list();
System.out.println(list); session.getTransaction().commit(); session.close();
}
|
【示例】
將訂單進行分頁查詢,每頁10條記錄,如今須要顯示第二頁的數據。
//將訂單進行分頁查詢,每頁10條記錄,如今須要顯示第二頁的數據。 @Test public void testQueryByPage(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //準備兩個變量 int page = 2; int pageCount = 10 ; //起始數:hibernate也是從0開始計數,因此起始條數不須要+1 int fromIndex = (page-1)*10;
//hql:分頁查詢方式,適用全部的數據庫 // List<Customer> list = session.createQuery("from Order") // //設置起始索引 // .setFirstResult(fromIndex) // //設置每頁查詢的條數 // .setMaxResults(pageCount) // .list();
//sql:注意區分數據庫:mysql的分頁使用limit關鍵,oracle的分頁至關複雜 // List list = session.createSQLQuery("select * from t_order limit ?,?") // .addEntity(Order.class) // .setInteger(0, fromIndex) // .setInteger(1, pageCount) // .list();
//qbc List<Order> list = session.createCriteria(Order.class) //起始索引 .setFirstResult(fromIndex) //每頁的條數 .setMaxResults(pageCount) .list();
System.out.println(list); session.getTransaction().commit(); session.close(); } |
【擴展oracle的sql語句的寫法】
//oracle:寫的技巧:先在sql編輯器中寫好,再複製進來改一改就好了。 List<Order> list2 = session.createSQLQuery("SELECT * FROM (SELECT t.*,ROWNUM r FROM t_order t WHERE ROWNUM<="+(firstResult+maxResults)+") t2 WHERE t2.r>="+(firstResult+1)).addEntity(Order.class).list(); System.out.println(list2); |
注意:若是用Hibernate技術,分頁推薦使用hql或qbc,由於能夠自動適應數據庫。
什麼是投影查詢?
投影查詢就是查詢結果僅包含實體的部分屬性,即只查詢表中的部分指定字段的值,不查詢所有。如:
select t.a,t.b,t.c from t;或者select count(*) from table; (是一種特殊的投影查詢)
投影的實現:
criteria.setProjection(須要查詢的屬性)
【示例】
查詢客戶的id和姓名。
//查詢客戶的id和姓名。 @Test public void testQueryByProjection(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
//hql:投影查詢返回是一個數組,不在一個是封裝好的對象, //在hibernate中,若是返回的是Object[]的話,那麼這個對象是不會存在於一級緩存的, // 是一個非受管對象(不受session管理) //List集合的長度是3: // 0 [1,'rose'] // 1 [2,'lucy'] // 2 [3,'jack'] // List<Object[]> list = session.createQuery("select c.id,c.name from Customer c").list(); //適用hql投影查詢的結果能夠封裝成一個對象,可是仍是一個非受管對象 //步奏 //1 去po中添加構造方法:空參構造+帶參構造 //2 從新編寫hql語句 // List<Customer> list = session.createQuery("select new Customer(c.id,c.name) from Customer c").list(); //sql // List<Object[]> list = session.createSQLQuery("select id,name from t_customer").list(); //qbc
List<Object[]> list = session.createCriteria(Customer.class) //設置投影,參數就是須要投影的屬性 .setProjection( //投影可能須要投影多個列,因此將多個列加入list集合,list集合是有序的 Projections.projectionList() //向projectionList中添加須要查詢的列 .add(Property.forName("id")) .add(Property.forName("name")) //瘋狂的追加投影的列 ).list();
for(Object[] obj:list) { System.out.println(obj[0]+":"+obj[1]); }
// System.out.println(list); session.getTransaction().commit(); session.close(); } |
【注意】
通過投影查詢的結果,默認都不會封裝到實體類型中,而是根據實際查詢的結果自動封裝(object[]),如查詢id和name,返回的object[]的list集合。
最大的壞處:一級緩存不存放該對象。沒法使用hibernate的一些特性,好比快照等等。
【注意】查詢以後封裝到Object[]數組中的這些數據,稱之爲散裝數據,不會存放於一級緩存,因此將來須要用的時候,還要查詢,儘可能少用
【應用提示】
實際hibernate開發中,通常較少使用投影查詢(除了統計).通常咱們都查詢出全部字段,讓其自動封裝到實體類中就好了.
【擴展閱讀】(瞭解)
投影查詢也能夠封裝到實體類中。(感興趣的同窗可查看課後文檔)
實體類:
qbc:
.setResultTransformer(Transformers.aliasToBean(Customer.class))
最終代碼:
//查詢用戶的id和姓名。 @Test //投影查詢:只查詢部分屬性的值 public void queryByProjection(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //結果集是根據返回的數據,自動封裝爲Object[],沒有封裝爲實體對象 // List<Object[]> list = session.createQuery("select id,name from Customer").list(); //若是要封裝爲實體對象,須要提供一個投影屬性的構造方法,不會再調用默認的構造器 //儘管被封裝爲實體對象,但該對象,是個非受管對象。不是被session管理 // List list = session.createQuery("select new Customer(id,name) from Customer").list(); // System.out.println(list); // for (Object[] obj : list) { // System.out.println(obj[1]); // }
//sql //結果集也是根據返回的數據的結果自動封裝爲Object[] List list2 = session.createSQLQuery("select id,name from t_customer") //設置結果集封裝策略 //相似於dbutil中的beanhandler,自動經過反射機制,自動將結果集封裝到指定的類型中 // .setResultTransformer(new AliasToBeanResultTransformer(Customer.class)) //官方提供了一個工具類,簡化代碼編寫 .setResultTransformer(Transformers.aliasToBean(Customer.class)) .list(); // ResultTransformer
System.out.println(list2);
//qbc List list3 = session.createCriteria(Customer.class) //設置投影列表 .setProjection(Projections.projectionList() //給屬性起別名 .add(Property.forName("id").as("id")) .add(Property.forName("name").as("name"))) //添加結果集的封裝策略 //發現了,該結果集封裝策略,是根據字段的別名來自動封裝 //解決方案:增長別名 .setResultTransformer(Transformers.aliasToBean(Customer.class)) .list(); // Projection // Property System.out.println(list3);
session.getTransaction().commit(); session.close();
}
|
小結:hibernate開發的狀況下,通常,不使用投影。,由於查詢出來的對象不被hibernate管理,它是是一個非受管對象。
沒法使用到hibernate的一些特性,好比快照更新等。
還有一種狀況,必須使用投影!統計的時候!(即便是統計的時候,投影也不是惟一的查詢方式)
舉例:查詢customer#2的訂單數量:customer.getOrders().size();
統計是一種特殊的投影查詢,因此結果也沒法封裝到實體,而是直接返回了統計後的結果值。
實現方式:
HQL和SQL使用統計函數/聚合函數,以下幾種:
QBC統計時是在投影方法參數中,使用Projections.rowCount()或者Projections.count(字段名)
sql語句中:
Count(*)
count(birthday):若是birthday=null,就不會做爲總的結果
【示例】
查詢客戶的總數
//查詢客戶的總數 @Test public void testQueryByCount(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
//hql:hql返回的結果集類型是Long // Object result = session.createQuery("select count(c) from Customer c").uniqueResult(); // long result = (Long) session.createQuery("select count(c) from Customer c").uniqueResult();
//sql:返回是BigInteger // Object result = session.createSQLQuery("select count(*) from t_customer").uniqueResult(); // BigInteger result = (BigInteger) session.createSQLQuery("select count(*) from t_customer").uniqueResult(); //qbc:返回Long類型 Object result = session.createCriteria(Customer.class) //rowCount:讀取全部的行數 // .setProjection(Projections.rowCount()) //讀取指定列的行數 ,這種讀取方式,當city爲null的時候,就不算一條記錄 .setProjection(Projections.count("city")) .uniqueResult();
System.out.println(result); session.getTransaction().commit(); session.close(); } |
【提示】
若是數據庫是oracle的話,sql方式返回的是BigDecimal
【示例】
查詢一下客戶編號爲1的客戶的訂單數量,要求只統計訂單的金額要大於等於30。(提示:綜合了條件查詢和統計查詢(投影))
@Test public void testPractise(){ //查詢一下客戶編號爲1的客戶的訂單數量,要求只統計訂單的金額要大於等於30 Session session = HibernateUtils.openSession(); session.beginTransaction(); // HQL:面向對象的查詢方式 // Object result = session.createQuery("select count(o) from Order o where o.price>=? and o.customer.id = ? ") // .setParameter(0, 30d) // .setParameter(1, 1) // .uniqueResult(); //不用投影,不用統計查詢 // int result = session.createQuery("from Order o where o.price>=? and o.customer.id = ? ") // .setParameter(0, 30d) // .setParameter(1, 1) // .list().size();
// SQL // Object result = session.createSQLQuery("select count(*) from t_order where price >=? and cid = ?") // .setParameter(0, 30d) // .setParameter(1, 1) // .uniqueResult();
// QBC:徹底的面向對象的方式操做數據庫
Customer customer = new Customer(); customer.setId(1); //不採用投影的方式 // int result = session.createCriteria(Order.class) // .add(Restrictions.ge("price", 30d)) // .add(Restrictions.eq("customer", customer)) // .list().size();
//採用投影的方式 Object result = session.createCriteria(Order.class) .add(Restrictions.ge("price", 30d))//設置條件 .add(Restrictions.eq("customer", customer))//設置條件 .setProjection(Projections.rowCount())//投影:只要統計結果的行數 .uniqueResult();
System.out.println(result); session.getTransaction().commit(); session.close();
} |
什麼是命名查詢?
命名查詢(NamedQuery),是指將sql或hql語句寫入配置文件中,爲該語句起個名字,在程序中經過名字來訪問sql或hql語句。
優勢:便於維護。
命名查詢的實現步驟:
第一步:在hbm中配置命名查詢的名字和語句(支持HQL或SQL)。
第二步:在程序中經過session.getNamedQuery(命名查詢的名字)來直接獲取Query或SQLQuery對象,進而進行查詢操做。
【示例】
查詢客戶的全部信息
Xml中的配置:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- 配置java類與表之間的對應關係 --> <!-- name:類名:類對應的完整的包路徑 table:表名 --> <class name="cn.itcast.a_onetomany.Customer" table="t_customer"> <!-- 配置主鍵 name:java類中的屬性 column:表中的字段,列名,當name和column一致的時候,column能夠省略 --> <id name="id" column="id"> <!-- 主鍵生成策略 mysql的自增加:identity --> <generator class="native"></generator> </id> <!-- 其餘屬性 name:java中的屬性 column:表中字段名 當name和column一致的時候,column能夠省略 --> <property name="name" column="name"></property> <!-- age :--> <property name="city"></property>
<!-- 配置集合 --> <set name="orders"> <!-- column:外鍵 --> <key column="cid"></key> <!-- 配置關係 class:集合中裝載對象的原型 --> <one-to-many class="cn.itcast.a_onetomany.Order"/> </set> <!-- hql :注意語句結束不能加";",不然報錯--> <query name="query1"> from Customer </query> <!-- sql --> <sql-query name="query2"> select * from t_customer </sql-query> </class>
<!-- name儘可能具備實際意義 --> <!-- hql :注意語句結束不能加";",不然報錯--> <query name="Customer.hql.queryall"> from Customer </query> <!-- sql --> <sql-query name="Customer.sql.queryall"> select * from t_customer </sql-query>
</hibernate-mapping>
|
testNameQuery方法的編寫:
@Test public void testNamedQuery(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //寫在class裏面,得經過包路徑調用 // List<Customer> list = session.getNamedQuery("cn.itcast.a_onetomany.Customer.query1").list(); //寫在class外面,就直接經過name調用 // List<Customer> list = session.getNamedQuery("query3").list();
//sql //class裏面 // SQLQuery sqlQuery = (SQLQuery) session.getNamedQuery("cn.itcast.a_onetomany.Customer.query2"); // List<Customer> list = sqlQuery.addEntity(Customer.class).list(); //寫在class外面 List<Customer> list = ((SQLQuery)session.getNamedQuery("query4")).addEntity(Customer.class).list();
System.out.println(list); session.getTransaction().commit(); session.close(); }
|
【提示】
命名查詢寫在<class>元素的內外是有區別的:
業務開發場景(閱讀):
在項目中:CRUD操做,查詢使用量最多,在實際中,都是根據條件查詢
條件不同,那麼咱們後臺是否是要寫不少查詢方法
根據城市查詢:queryByCity
根據用戶名查詢:queryByName
根據年齡查詢:queryByAge
-----dao中方法很重複
離線查詢:他容許在業務層(service)去拼裝條件,而後直接將條件傳入dao層的方法,運行,
這時候,dao層只須要一個方法,就能夠完成查詢
在常規的Web編程中,有大量的動態條件查詢,即用戶在網頁上面自由選擇某些條件,程序根據用戶的選擇條件,動態生成SQL語句,進行查詢。 針對這種需求,對於分層應用程序來講,Web層須要傳遞一個查詢的條件列表給業務層對象,業務層對象得到這個條件列表以後,而後依次取出條件,構造查詢語句。這裏的一個難點是條件列表用什麼來構造?傳統上使用Map,可是這種方式缺陷很大,Map能夠傳遞的信息很是有限,只能傳遞name和value,沒法傳遞究竟要作怎樣的條件運算,到底是大於,小於,like,仍是其它的什麼,業務層對象必須確切掌握每條entry的隱含條件。所以一旦隱含條件改變,業務層對象的查詢構造算法必須相應修改,可是這種查詢條件的改變是隱式約定的,而不是程序代碼約束的,所以很是容易出錯。 DetachedCriteria能夠解決這個問題,即在web層,程序員使用DetachedCriteria來構造查詢條件,而後將這個DetachedCriteria做爲方法調用參數傳遞給業務層對象。而業務層對象得到DetachedCriteria以後,能夠在session範圍內直接構造Criteria,進行查詢。就此, WEB層只須要添加條件,不須要考慮查詢語句如何編寫,而業務層則只負責完成持久化和查詢的封裝便可,與查詢條件構造徹底解耦,很是完美! 最大的意義在於,業務層或dao層代碼是固定不變的,全部查詢條件的構造都在web層完成,業務層只負責在session內執行之。這樣代碼就可放之四海而皆準,都無須修改了。 |
【區別】:
Criteria:在線查詢方式:依賴session,有了session以後,才能去建立Criteria對象,而後才能夠添加條件
DetachedCriteria:離線查詢方式:不依賴session建立,自身內部含有建立方式,能夠在沒有session的狀況下,自由的組裝各類條件,
而後在發送給session執行
API的查看:
經過API分析,獲得編程關鍵點:
DetachedCriteria是Criteria的子實現,經過靜態方法DetachedCriteria.forClass(PO.class)來實例化,它能夠像Criteria的對象同樣增長各類查詢條件,經過detachedCriteria.getExecutableCriteria(session)方法與session關聯,變成在線Criteria對象,最後經過criteria.list()方法獲得數據。
【示例】
查詢id值大等於2且城市是杭州的客戶信息。
@Test public void testDetachedCriteria(){ //查詢id值大等於2且城市是杭州的客戶信息。 //模擬service層 DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class); //拼命的加條件 // detachedCriteria.add(Restrictions.ge("id", 2)); // detachedCriteria.add(Restrictions.eq("city", "杭州"));
//查詢名字帶有c的人員的信息 detachedCriteria.add(Restrictions.like("name", "%c%"));
//模擬dao層:dao層就固定寫法 Session session = HibernateUtils.openSession(); session.beginTransaction(); //執行離線查詢,傳入session Criteria criteria = detachedCriteria.getExecutableCriteria(session); List<Customer> list = criteria.list();
System.out.println(list);
session.getTransaction().commit(); session.close(); }
|
【Criteria和DetachedCriteria的區別】
Criteria和DetachedCriteria 的主要區別在於建立的形式不同, Criteria 是在線的,因此它是由 Hibernate Session 進行建立的;而 DetachedCriteria 是離線的,建立時無需Session,DetachedCriteria 提供了 2 個靜態方法 forClass(Class) 或 forEntityName(Name)進行DetachedCriteria 實例的建立。使用getExecutableCriteria(session)方法轉換成在線可執行的Criteria
多表關聯的分類:
三種鏈接方式的sql語句和結果:
[內鏈接]: select * from customer t1, order t2 where t1.id=t2.customer_id;--並非標準-隱式的內鏈接 select * from customer t1 inner join order t2 on t1.id=t2.customer_id;--sql99標準語法-顯示內鏈接 查詢結果: 1 Rose 1001 電視機 1
[左外鏈接]:以左表爲基表,右表來關聯左表,若是右表沒有與左表匹配的數據,則右表顯示null select * from customer t1 left outer join order t2 on t1.id =t2.customer_id;//左外鏈接
查詢結果: 1 Rose 1001 電視機 1 2 Jack null null null
[右鏈接] select * from customer t1 right join order t2 on t1.id=t2.customer_id;//右鏈接 查詢結果: 1 Rose 1001 電視機 1 null null 1002 空調 null |
#內鏈接:項目中使用最多的鏈接方式 #方式一:錶鏈接的工做就是先進性笛卡爾乘積,而後在篩選 SELECT c.*,o.* FROM t_customer c,t_order o WHERE c.id=o.cid; #方式二 SELECT c.* ,o.* FROM t_customer c INNER JOIN t_order o ON c.id = o.cid;
# 左外鏈接(左鏈接):以左表爲基表,右表來匹配左表,左表數據所有顯示,當右表沒有與之匹配的數據的時候,直接用null代替 SELECT c.*,o.* FROM t_customer c LEFT JOIN t_order o ON c.id = o.cid; #左鏈接實際應用場景:查詢歷來沒有買過東西的客戶信息 SELECT c.*,o.* FROM t_customer c LEFT JOIN t_order o ON c.id = o.cid WHERE o.id IS NULL;
# 右外鏈接(右鏈接):以右表爲基表,左表來匹配右表,右表數據所有顯示,當左表沒有與之匹配的數據的時候,直接用null代替 SELECT o.*,c.* FROM t_order o RIGHT JOIN t_customer c ON c.id = o.cid; |
【提示】
HQL支持普通鏈接(內鏈接、左外鏈接),但也支持迫切鏈接(迫切內鏈接、迫切左外鏈接)。
QBC和SQL都只支持普通鏈接(內鏈接、左外鏈接)。
【學習目標提醒】
主要目標是學習鏈接和迫切鏈接的異同。
回顧導航查詢的缺點:
會先查詢主po對象,發出一條語句,再訪問關聯屬性的時候,再發出一條語句,須要兩次查詢。
若是,我想一次性拿到客戶和關聯的訂單,我就可使用多表關聯查詢。僅使用一條sql語句,就能夠獲得兩個表(對象)的數據,效率比兩次查詢高!
【示例】
查詢全部客戶信息和對應的全部訂單信息,要求一條語句就將兩張表的結果查詢出來(提示:內鏈接或迫切內鏈接)。
//查詢全部客戶信息和對應的全部訂單信息,要求一條語句就將兩張表的結果查詢出來 @Test public void testQueryForManyTable(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); // HQL:採用HQL進行鏈接,不須要加條件,由於條件已經在hbm.xml中定義好了 //內鏈接返回的是數組對象 (數組是一種散裝對象,不會存入session緩存,不具有快照特性) // List list = session.createQuery("from Customer c inner join c.orders").list(); //迫切鏈接將返回的結果封裝爲實體對象 // List<Customer> list = session.createQuery("from Customer c inner join fetch c.orders").list(); //經過觀察,咱們發現,結果重複,接下來去除重複:濾重 //distinct:去處重複的關鍵字(hibernate,mysql和oracle都用這個關鍵字去重) List<Customer> list = session.createQuery("select distinct(c) from Customer c inner join fetch c.orders").list();
System.out.println(list); session.getTransaction().commit(); session.close(); } |
【結果】
【分析內鏈接和迫切內鏈接的異同】
【擴展瞭解】
和SQL語句同樣,內鏈接或迫切內鏈接的語句中的inner關鍵字能夠省略。
【問題】
迫切內鏈接返回的結果是重複的,可以使用distinct關鍵字濾重。
【示例】
一次性查詢出全部客戶信息以及其所下的訂單的信息,要求結果被封裝到客戶的實體對象中,而且返回的對象不要重複。
List<Customer> list13 = session.createQuery("select distinct (c) from Customer c join fetch c.orders").list();
|
【提示】
若是要用迫切鏈接查詢的話,結果須要去除重複的。
左外鏈接(左鏈接)和迫切左外鏈接:
//左外鏈接:返回的結果是一個Object[]的數組對象 // List list15 = session.createQuery("from Customer c left join c.orders").list(); //迫切左外鏈接:返回的結果是一個封裝好的Customer對象 List list15 = session.createQuery("select distinct c from Customer c left join fetch c.orders").list(); System.out.println(list15); |
【示例】封裝以後的數據,只能是Object[]類型的數組,是一個非受管對象
一次性查詢出客戶信息和其下的訂單信息。(提示:沒法實現實體的徹底封裝)
//內鏈接--沒有迫切一說 //sql //普通的內鏈接 List list = session.createSQLQuery("select * from t_customer t1 inner join t_order t2 on t1.id =t2.cid") // .addEntity(Customer.class)//封裝到實體 .addEntity(Order.class)//封裝到實體,發現封裝後會丟失數據 .list(); |
【提示:】
SQL只有內鏈接查詢,沒有迫切內鏈接查詢。所以,沒法實現一次性將主對象和關聯對象一次性查詢出來的需求。
Criteria接口提供createCriteria和createAlias兩組方法用於完成多表關聯查詢
提示:qbc沒有迫切查詢
QBC採用createCriteria()很是容易的在互相關聯的實體間創建鏈接關係。
從名字上看,貌似是建立一個子的creaiteria,可是生成的語句能夠是內鏈接或左鏈接的.
【示例】
一次性查詢出全部用戶及其所下的訂單信息。
一次性查詢出某用戶(id=1)所下訂單的信息,而且要求訂單價格大於50元。
//一次性查詢出全部用戶及其所下的訂單信息。 @Test public void testQueryForManyTableByCriteria1(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //主查詢 Criteria criteria = session.createCriteria(Customer.class); //鏈接對象:默認是內鏈接 criteria.createCriteria("orders"); // 因爲查詢結果是重複的,因此在list方法執行以前必定要濾重, //設置重複結果過濾 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); //進行查詢 //返回的結果到底封裝成什麼類型,主要看主查詢的參數 List<Customer> list = criteria.list();
System.out.println(list); session.getTransaction().commit(); session.close();
}
//一次性查詢出某用戶(id=1)所下訂單的信息,而且要求訂單價格大於50元。 @Test public void testQueryForManyTableByCriteria2(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //主查詢:訂單 Criteria criteria = session.createCriteria(Order.class); // /添加條件 criteria.add(Restrictions.gt("price", 50d)); //子查詢:默認是內鏈接 Criteria childCriteria = criteria.createCriteria("customer"); //添加條件 childCriteria.add(Restrictions.eq("id", 1));
//執行查詢 List<Order> list = criteria.list();
System.out.println(list); session.getTransaction().commit(); session.close();
}
|
【控制檯結果】
【QBC的優點】
條件越多,編碼起來相對越簡單一些,只須要在criteria上加條件便可,而不須要關心語句該怎麼寫。
[擴展:更改qbc的結果集的重複封裝的問題]
Hibernate推薦使用HQL和QBC,二者區別:
但企業開發中,若是爲了sql語句的性能,會直接採用SQL進行開發。若是爲了封裝方便(好比離線查詢條件封裝),也會採用QBC。具體根據項目架構來決定。
可是這不是Query主要職責,他的只要職責仍是查詢,並且他作增長的時候,還有缺陷:
它不能執行 insert into table(,,,) values(,,)
只支持INSERT INTO ... SELECT ...形式
Query接口也能夠接受insert、update、delete語句的執行。
//Query也能夠執行insert,update,delete //場景,不根據id來更新,不根據id刪除,想建立一張表 @Test //query對象的使用擴展 public void queryObjExtend(){ Session session = HibernateUtils.openSession(); session.beginTransaction(); //hql //根據名稱更新客戶->query不止是能夠查詢,也能夠執行任何的語句 //該方法更新,不走一級緩存,直接操做數據庫了,至關於之前connection了 // Query query = session.createQuery("update Customer set city='海南島' where name='xiaohong'"); // //執行query // int count = query.executeUpdate(); // System.out.println(count);
//sql session.createSQLQuery("create table t_test (name varchar(30))").executeUpdate();
session.getTransaction().commit(); session.close(); } |
但insert只支持:hql 只支持INSERT INTO ... SELECT ...形式, 不支持INSERT INTO ... VALUES ...形式.
原理是:query仍是以查詢爲基礎的
抓取策略的官方定義:
簡單的說:Hibernate抓取策略(fetching strategy)是指:在檢索一個對象,或者在對持久態對象經過對象導航方式來獲取關聯對象屬性的數據時,Hibernate的相關檢索策略。抓取策略能夠在hbm映射文件中配置聲明,也能夠在HQL語句中進行覆蓋(即前面寫的迫切左外語句,缺點代碼耦合太強,可配置性差)。
根據數據的抓取方式分爲:
類級別的抓取策略就兩種:當即檢索和延遲檢索
當即檢索:get(第二次查詢會從一級緩存中查詢),createQuery(hql).list(每次都查詢,每次都發查詢語句)
延遲檢索:load(第二次查詢會從一級緩存中查詢)
建立包cn.itcast.c_fetchingstrategy,建立類TestStrategy,而後測試
//加載customer信息 //get:默認當即加載 Customer c1 = (Customer)session.get(Customer.class, 1); System.out.println(c1); //load:默認延遲加載 Customer c2 = (Customer)session.load(Customer.class, 2); System.out.println(c2); |
load默認返回目標類的代理對象的子類對象,沒有發送sql(即沒有初始化),只有當訪問的時候才初始化。
load延遲加載是否能夠改變呢?
經過hbm文件的<class>元素的lazy屬性進行設置(默認值是true)
再次測試上面的例子。
發現load也變成當即加載了。
結論:lazy=false的時候,類採用當即加載策略,load和get效果同樣了。
狀況一:當訪問代理對象id以外的屬性的時候
//load:默認延遲加載,什麼時候被初始化呢? Customer c2 = (Customer)session.load(Customer.class, 2); System.out.println(c2.getId());//訪問id的時候不會初始化 System.out.println(c2);//當訪問其餘屬性的時候,自動初始化 |
狀況二:使用Hibernate工具類的initialize方法強制初始化代理對象--瞭解
Customer c2 = (Customer)session.load(Customer.class, 2); Hibernate.initialize(c2);//強制初始化 |
小結:若是真要用強制初始化。,那還不如直接用get進行查詢
當訪問對象的延遲加載時,底層也是調用Hibernate工具類的initialize方法
若是使用HQL進行查詢,即便配置了延遲加載,也無效
【示例】
採用createQuery查詢一個對象,無懶加載特色。(即便配置了懶加載也無效)
Customer c3=(Customer)session.createQuery("from Customer where id =2").uniqueResult(); System.out.println(c3); |
【提示】
這裏能夠看出,Query對象的查詢都是當即加載,並當即發出用戶定義好的SQL,並且必定會發出(不從一級緩存中獲取)。
HQL的兩個特色:馬上加載 ; 不走一級緩存
【解釋說明】在一對多的關係中,在一方,配置了集合,咱們研究集合的初始化的時機,在默認狀況下,集合在你須要使用的時候,纔會初始化,不使用,就不會初始化。固然,對於這個默認的結果,是能夠改變的,如何改變呢?
主要使用:
<set> 元素提供fetch屬性和lazy屬性 用於設置 集合 抓取策略
關於fetch和lazy的做用:
語言精簡一下,記住:
fetch是控制sql語句的生成方式,(1 錶鏈接、2 子查詢、3須要的時候查詢)
lazy是控制數據初始化的時間。
一對多或多對多方向關聯的檢索策略表: (配置在set標籤上的)
【提示】
通過分析發現Fetch:屬性的值有3個,Lazy屬性的值也有3個,這兩個屬性是要同時配置的,有9種組合。
爲方便學習,咱們將根據fetch的值的狀況,將其分爲三類組合:
第一類組合:
fetch |
語句形式 |
lazy |
數據初始化的時間 |
select |
多條簡單SQL語句 |
true |
延遲加載(默認值) |
false |
當即加載 |
||
extra |
加強的延遲加載(極其懶惰) |
lazy=extra的說明:當程序調用orders 屬性的 size(), contains() 和 isEmpty() 方法時, Hibernate 不會初始化orders集合類中全部子對象的實例,
僅經過特定的 select 語句查詢必要的信息, 不會檢索全部的 Order 對象。
設置方法:
在採用<one-to-many>元素的父元素(如set)中設置fetch和lazy屬性的值。
在customer.hbm.xml中設置一下默認屬性的值:
【測試示例代碼】
@Test public void testFetchAndLazy(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer.getOrders().size());
session.getTransaction().commit(); session.close(); } |
第二類組合:
fetch |
語句形式 |
lazy |
數據初始化的時間 |
join |
迫切左外鏈接SQL語句 |
true |
所有忽略失效。 |
false |
|||
extra |
【進一步】
經過策略列表發現,只要是fetch是join就是迫切左外鏈接,而迫切左外鏈接就會當即加載其屬性, lazy屬性被忽略. (如Customer left join fetch orders當即查詢客戶和訂單數據)
【示例】
@Test public void testFetchAndLazy(){ Session session = HibernateUtils.openSession(); session.beginTransaction();
Customer customer = (Customer)session.get(Customer.class, 1);
System.out.println(customer.getOrders().size());
session.getTransaction().commit(); session.close(); } 控制打印的語句: |
第三種組合:(瞭解)
fetch |
語句形式 |
lazy |
數據初始化的時間 |
subselect |
子查詢的sql語句(效率偏低,通常不採用) |
true |
所有忽略失效。 |
subselect |
【示例】
List<Customer> list = session.createQuery("from Customer").list(); for (Customer customer : list) { System.out.println(customer.getOrders().size()); } |
使用createQuery自定義HQL查詢語句時,fetch就會被直接忽略(失效),而lazy會根據語句的編寫狀況能夠有效,也能夠無效。
也就是說,語句的格式已經定死了,fetch沒法改變了,就會失效。而語句若是是採用多表鏈接查詢,那麼lazy也會無效;但如果語句只是查詢一個對象,那麼其關聯屬性的lazy依然有效(由於是通常的導航查詢)
採用HQL的時候,fetch直接失效
Lazy看狀況:當hql語句只是查詢單個簡單的對象,lazy依然有效
當hql是進行多表查詢的時候,lazy也會失效
【示例】
//fetch確定是失效,lazy有效 session.createQuery("from Customer"); //fetch和lazy都失效 session.createQuery("from Customer c inner join c.orders"); |
【總結】
多對一抓取策略:經過多的一方(Order)來導航查詢一的一方(Customer)的策略。
抓取一方的時機
關係元素(<many-to-one>)中提供fetch屬性和lazy屬性 用於設置 抓取策略,如:
在Order.hbm.xml <many-to-one>中配置。
咱們將根據fetch的值的狀況,將其分爲兩類組合:
【測試示例】
查詢某訂單,而且要顯示其所屬的客戶信息。
分析:查詢主體是訂單
//查詢1號訂單,而且要顯示其所屬的客戶信息。 Order o =(Order)session.get(Order.class, 1); System.out.println(o); System.out.println(o.getCustomer()); |
第一類組合:
fetch |
語句形式 |
lazy |
數據初始化的時間 |
select |
多條簡單SQL語句 |
Proxy (分狀況討論) |
根據關聯對象的類級別抓取策略來決定是否延遲加載(默認值) <class name="..Customer" lazy=true >:延遲加載 <class name="..Customer" lazy=false>:當即加載 |
false |
當即加載 |
第二類組合:
fetch |
語句形式 |
lazy |
數據初始化的時間 |
join |
迫切左外鏈接SQL語句 |
proxy |
所有忽略失效。 |
false |
【示例】
@Test public void testFetchAndLazy_manyToOne(){ //查詢某訂單,而且要顯示其所屬的客戶信息。 Session session = HibernateUtils.openSession(); session.beginTransaction();
Order order = (Order) session.get(Order.class, 1); System.out.println(order); System.out.println(order.getCustomer());
session.getTransaction().commit(); session.close(); } |
使用createQuery自定義HQL查詢語句時,fetch就會被直接忽略(失效),而lazy會根據語句的編寫狀況能夠有效,也能夠無效。
也就是說,語句的格式已經定死了,fetch沒法改變了,就會失效。而語句若是是採用多表鏈接查詢,那麼lazy也會無效;但若是語句只是查詢一個對象,那麼其關聯屬性的lazy依然有效(由於是通常的導航查詢)
【示例】
若是採用Query查詢,不會自動生成左外鏈接(query是本身寫的語句),fetch=join 被忽略,lazy能夠生效
//fetch無效,lazy有效 session.createQuery("from Order where id = 1"); //fetch和lazy都無效 session.createQuery("from Order o inner join o.customer"); |
【總結】當使用HQL的時候,不少的配置會直接失效
1.設置類級別抓取策略 ,能夠經過修改 hbm文件 <class>元素 lazy屬性來實現,值能夠是:true延遲,false 當即:
2.設置關聯級別抓取策略:
延遲加載的好處是:沒有當即加載數據,當須要的時候再加載,提升了內存的使用率,優化了程序的效率!
所以,在通常狀況下,能延遲加載的儘可能延遲,默認狀況下都是延遲的。這也是框架默認的。
默認的處理方式已是很是優秀了,不多須要改。
但,還要根據具體業務開發中的須要,若是這些數據就是須要當即展現,那麼就優先使用fetch=join迫切左外鏈接查詢加載數據。
Fetch+lazy的組合
Select+lazy:效果較多
Join+lazy:lazy失效,直接錶鏈接
下面幾道題目用來理解抓取策略:
問題:發出了幾條sql語句
//上述案例的驗證核心代碼:
Order order =(Order)session.get(Order.class, 1); Customer customer=order.getCustomer(); customer.getName ();
|
分析:
分析:
【由此看出】
sql語句是由類級別抓取策略和關聯集合的抓取策略共同決定的
批量抓取(Batch fetching):對查詢抓取的優化方案,經過指定一個主鍵或外鍵列表,Hibernate使用單條的select語句獲取一批對象實例或集合的策略.
批量抓取的目的:爲了解決
.(或稱之爲1+N)的問題。(主要是針對導航查詢)
什麼是N+1?請看下面的示例。(下面咱們也將分爲一對多和多對一兩個方向進行講解。)
【需求】
查詢全部客戶和其所下的訂單的數量狀況
//查詢全部客戶和其訂單的數量狀況 List<Customer> list = session.createQuery("from Customer").list();
for(Customer c:list) { System.out.println(c.getName()+":"+c.getOrders().size()); } |
思考:會產生多少條語句呢?
3個客戶,4條語句。
先查客戶一條+3次訂單查詢。
這就是N+1
並且從打印的語句看後面4條都差很少。
問題:若是是1w個用戶呢?會發送10001條語句。(每一個用戶查詢訂單 SQL語句格式是相同的)
優化方案:
設置:
問題:那這個值如何設定呢?
值的設定根據你的需求(你前臺的頁面來了)來的,好比你的條件就每次查詢10條,那就配置爲10。
【需求】
查詢全部訂單信息,並打印對應的客戶姓名
List<Order> list = session.createQuery("from Order").list();
for(Order o:list) { System.out.println(o.getName()+":"+o.getCustomer().getName()); } |
思考:會產生多少條語句呢?
4條語句------>查詢訂單一條+查詢對應的客戶3條(一級緩存致使不是30條)
優化方案:
注意,仍是在customer.hbm.xml中配置:
仍是在一的一方的class標籤上配置的。
優化後變成2條
小結:batch-size到底設置多少?根據你的頁面顯示的數量來調整。好比你頁面每次就10條,那麼你能夠將該值設置爲10。
該值,也不能太大,太大可能會致使內存溢出,要根據實際狀況來設置。
Fetch=join:直接採用錶鏈接
【做業一】
完成全天課程練習。
【做業二】
次日的課前練習(未完成的部分)