Hibernate4實戰 之 第三部分:Hibernate的基本開發 – JAVA編程語言程序開發技術文章

1:瞬時(Transient) – 由new操作符創建,且尚未與Hibernate Session 關聯的對象被認定為瞬時的。瞬時對象不會被持久化到數據庫中,也不會被賦予持久化標識(identifier)。 如果瞬時對象在程序中沒有被引用,它會被垃圾回收器銷毀。 使用Hibernate Session可以將其變為持久狀態,Hibernate會自動執行必要的SQL語句。
 
2:持久(Persistent) – 持久的實例在數據庫中有對應的記錄,並擁有一個持久化標識。 持久的實例可能是剛被保存的,或剛被加載的,無論哪一種,按定義,它存在於相關聯的Session作用范圍內。 Hibernate會檢測到處於持久狀態的對象的任何改動,在當前操作單元執行完畢時將對象數據與數據庫同步。開發者不需要手動執行UPDATE。將對象從持久狀態變成瞬時狀態同樣也不需要手動執行DELETE語句。
 
3:脫管(Detached) – 與持久對象關聯的Session被關閉後,對象就變為脫管的。 對脫管對象的引用依然有效,對象可繼續被修改。脫管對象如果重新關聯到某個新的Session上, 會再次轉變為持久的,在脫管期間的改動將被持久化到數據庫。
 
通過Session接口來操作Hibernate
新增——save方法、persist方法
1:persist() 使一個臨時實例持久化。然而,它不保證立即把標識符值分配給持久性實例,這會發生在flush的時候。persist() 也保證它在事務邊界外調用時不會執行INSERT 語句。這對於長期運行的帶有擴展會話/持久化上下文的會話是很有用的。
2:save() 保證返回一個標識符。如果需要運行INSERT 來獲取標識符(如"identity" 而非"sequence" 生成器),這個INSERT 將立即執行,不管你是否在事務內部還是外部。這對於長期運行的帶有擴展會話/持久化上下文的會話來說會出現問題。
 
刪除——delete方法
修改——有四種方法來做,分別是:
1:直接在Session打開的時候load對象,然後修改這個持久對象,在事務提交的時候,會自動flush到數據庫中。
2:修改托管對象,可用update或merge方法
3:自動狀態檢測:saveOrUpdate方法
updatemerge方法
1:如果數據庫裡面存在你要修改的記錄,update每次是直接執行修改語句;而merge是先在緩存中查找,緩存中沒有相應數據,就到數據庫去查詢,然後再合並數據,如果數據是一樣的,那麼merge方法不會去做修改,如果數據有不一樣的地方,merge才真正修改數據庫。
2:如果數據庫中不存在你要修改的記錄,update是報錯;而merge方法是當作一條新增的值,向數據庫中新增一條數據。
3:update後,傳入的TO對象就是PO的瞭,而merge還是TO的。
4:如果你確定當前session沒有包含與之具有相同持久化標識的持久實例,使用update()。如果想隨時合並改動而不考慮session的狀態,使用merge()。換句話說,在一個新session中通常第一個調用的是update()方法,以保證重新關聯脫管對象的操作首先被執行。
5:請註意:使用update來把一個TO變成PO,那麼不管是否修改瞭對象,都是要執行update sql語句的。
 
 
 
 

通常下面的場景會使用 update() 或 saveOrUpdate()
1:程序在第一個 session 中加載對象
2:該對象被傳遞到表現層
3:對象發生瞭一些改動
4:該對象被返回到業務邏輯層
5:程序調用第二個session的update()方法持久這些改動
 
saveOrUpdate方法做下面的事:
1:如果對象已經在本session中持久化瞭,不做任何事
2:如果另一個與本session關聯的對象擁有相同的持久化標識,拋出一個異常
3:如果對象沒有持久化標識屬性,對其調用 save()
4:如果對象的持久標識表明其是一個新實例化的對象,對其調用 save()。
5:如果對象是附帶版本信息的(通過 <version> 或 <timestamp>)並且版本屬性的值表明其是一個新實例化的對象,save() 它。
6:否則update()這個對象
 
merge做如下的事情
1:如果session中存在相同持久化標識的實例,用用戶給出的對象的狀態覆蓋舊有的持久實例
2:如果session中沒有相應的持久實例,則嘗試從數據庫中加載,或創建新的持久化實例
3:最後返回該持久實例
4:用戶給出的這個對象沒有被關聯到 session 上,它依舊是脫管的
 
按主鍵查詢
1:load方法:load的時候首先查詢一級緩存,沒有就創建並返回一個代理對象,等到使用的時候,才查二級緩存,如果二級緩存中沒有數據就查數據庫,如果數據庫中沒有,就拋例外
2:get方法:先查緩存,如果緩存中沒有這條具體的數據,就查數據庫,如果數據庫沒有值,就返回null,總之get方法不管用不用,都要拿到真實的數據
Hibernate實現按條件查詢的方式
1:最重要的按條件查詢的方法是使用Query接口,使用HQL
2:本地查詢(native sql):就是使用標準的sql,也是通過Query接口來實現
3:按條件查詢(Query By Criteria,QBC):使用動態的,面向對象的方式來創建查詢
4:按樣例查詢(Query By Example,簡寫QBE):類似我們自己寫的getByCondition
5:命名查詢:在hbm.xml中配置hql語句,在程序裡面通過名稱來創建Query接口
Query的list方法
一個查詢通常在調用 list() 時被執行,執行結果會完全裝載進內存中的一個集合,查詢返回的對象處於持久狀態。如果你知道的查詢隻會返回一個對象,可使用 list() 的快捷方式 uniqueResult()。
Iterator和List
某些情況下,你可以使用iterate()方法得到更好的性能。 這通常是你預期返回的結果在session,或二級緩存(second-level cache)中已經存在時的情況。 如若不然,iterate()會比list()慢,而且可能簡單查詢也需要進行多次數據庫訪問: iterate()會首先使用1條語句得到所有對象的持久化標識(identifiers),再根據持久化標識執行n條附加的select語句實例化實際的對象。
 
外置命名查詢
可以在映射文件中定義命名查詢(named queries)。

java代碼:
1. <query name="javass"> 
2. <![CDATA[select Object(o) from UserModel o]]> 
3. </query> 
參數綁定及執行以編程方式完成:
List list = s.getNamedQuery("cn.javass.h3.hello.UserModel.javass").list();
註意要用全限定名加名稱的方式進行訪問
 
flush方法
每間隔一段時間,Session會執行一些必需的SQL語句來把內存中對象的狀態同步到JDBC連接中。這個過程被稱為刷出(flush),默認會在下面的時間點執行:
1:在某些查詢執行之前
2:在調用org.hibernate.Transaction.commit()的時候
3:在調用Session.flush()的時候
涉及的 SQL 語句會按照下面的順序發出執行:
1. 所有對實體進行插入的語句,其順序按照對象執行save() 的時間順序
2. 所有對實體進行更新的語句
3. 所有進行集合刪除的語句
4. 所有對集合元素進行刪除,更新或者插入的語句
5. 所有進行集合插入的語句
6. 所有對實體進行刪除的語句,其順序按照對象執行 delete() 的時間順序
除非你明確地發出瞭flush()指令,關於Session何時會執行這些JDBC調用是完全無法保證的,隻能保證它們執行的前後順序。 當然,Hibernate保證,Query.list(..)絕對不會返回已經失效的數據,也不會返回錯誤數據。
lock方法:也允許程序重新關聯某個對象到一個新 session 上。不過,該脫管對象必須是沒有修改過的。示例如:s.lock(um, LockMode.READ);
註意:lock主要還是用在事務處理上,關聯對象隻是一個附帶的功能
 
獲取元數據
Hibernate 提供瞭ClassMetadata接口和Type來訪問元數據。示例如下:

java代碼:
查看復制到剪貼板打印
1. ClassMetadata catMeta = sf.getClassMetadata(UserModel.class); 
2. String[] propertyNames = catMeta.getPropertyNames(); 
3. Type[] propertyTypes = catMeta.getPropertyTypes(); 
4. for (int i = 0; i < propertyNames.length; i++) { 
5. System.out.println("name=="+propertyNames[i] + ", type==“ 
6.                       +propertyTypes[i]); 
7. } 
8.   
HQL介紹
Hibernate配備瞭一種非常強大的查詢語言,這種語言看上去很像SQL。但是不
要被語法結構 上的相似所迷惑,HQL是非常有意識的被設計為完全面向對象的查
詢,它可以理解如繼承、多態 和關聯之類的概念。
看個示例,看看sql和HQL的相同與不同:
Sql:select    *      from tbl_user    where uuid=‘123’
HQL:select Object(o) from UserModel o where o.uuid=‘123’
HQL特點
1:HQL對Java類和屬性是大小寫敏感的,對其他不是大小寫敏感的。
2:基本上sql和HQL是可以轉換的,因為按照Hibernate的實現原理,最終運行的還是sql,隻不過是自動生成的而已。
3:HQL支持內連接和外連接
4:HQL支持使用聚集函數,如:count、avg、sum、min、max等
5:HQL支持order by 和 group by
6:HQL支持條件表達式,如:in、like、between等
select子句
1:直接返回對象集合,形如:select o from UserModel o
2:返回某個特定類型的集合,形如:select o.name from UserModel o
3:返回Object[],形如:select o.uuid,o.name from UserModel o
4:返回List,形如:select new List(o.uuid,o.name) from UserModel o
5:返回任意的對象,形如:select new cn.javass.h3.hello.A(o.uuid,o.name) from UserModel o ,這要求A對象有一個構造方法是傳入這兩個參數
6:返回Map類型,形如:select new Map(o.uuid as Id,o.name as N) from UserModel o ,返回的結果,以as後面的別名做map的key,對應的數據做值
 
from子句
1:直接from對象,形如: from UserModel
2:可以分配別名,形如:from UserModel as um  , as關鍵字可以省略
3:如果from後面有多個對象,形如:from UserModel,DepModel ,相當於多表聯合查詢,返回他們的笛卡爾積
 
聚集函數
1:受支持的有avg,sum,min,max,count
2:關鍵字 distinct 與all 也可以使用,它們具有與 SQL 相同的語義,比如:
select count(distinct o.name) from UserModel o
nwhere子句
1:如果前面沒有指派別名,那就直接使用屬性名
2:如果指派瞭別名,必須使用別名.屬性的方式
3:在where子句中允許使用的表達式包括大多數在 SQL中使用的表達式,包括:
(1)數學運算符 +,-,*,/
(2)二進制比較運算符 =, >=, <=, <>, !=, like
(3)邏輯運算符 and,or,not
(4)括號 ( ),表示分組
(5)in, not in, between, is null, is not null, is empty, is not empty, member of and not member of
(6)字符串連接符 …||… or concat(…,…)
(7)current_date(), current_time(), and current_timestamp()
(8)second(…)、minute(…)、hour(…)、day(…)、month(…) 和 year(…)
(9)EJB-QL 3.0 定義的任何功能或操作符:substring(), trim(), lower(), upper(), length(),locate(), abs(), sqrt(), bit_length(), mod()
(10)coalesce() 和 nullif()
(11)str() 把數字或者時間值轉換為可讀的字符串
(12)cast(… as …),其第二個參數是某 Hibernate 類型的名字,以及 extract(… from …),隻要 ANSI cast() 和 extract() 被底層數據庫支持
(13)HQL index() 函數,作用於 join 的有序集合的別名。
(14)HQL 函數,把集合作為參數:size(), minelement(), maxelement(), minindex(), maxindex(),還有特別的 elements() 和 indices 函數,可以與數量詞加以限定:some, all, exists, any, in。
(15)任何數據庫支持的 SQL 標量函數,比如 sign(), trunc(), rtrim(), sin()
(16)JDBC 風格的參數傳入 ?
(17)命名參數 :name,:start_date,:x1
(18)SQL 直接常量 'foo', 69, 6.66E+2, '1970-01-01 10:00:01.0'
(19)Java public static final 類型的常量 eg.Color.TABBY
group by 子句
1:對於返回聚集值的查詢,可以按照任何屬性進行分組
2:可以使用having子句
3:sql中的聚集函數,可以出現在having子句中
4:group by 子句與 order by 子句中都不能包含算術表達式
5:不能group by 某個實體對象,必須明確的列出所有的聚集屬性
 
order by 子句
查詢返回的列表(list)可以按照一個返回的類或組件(components)中的任何屬性進行排序,可選的 asc 或 desc 關鍵字指明瞭按照升序或降序進行排序。
 
子查詢
對於支持子查詢的數據庫,Hibernate 支持在查詢中使用子查詢。一個子查詢必須被圓括號包圍起來。
n連接(join)
1:Hibernate可以在相關聯的實體間使用join,類似於sql,支持inner join、left outer join、right outer join、full join(全連接,並不常用)。
2:inner join可以簡寫成join,left outer join 和right outer join在簡寫的時候可以把outer去掉。
with
通過 HQL 的 with 關鍵字,你可以提供額外的 join 條件。
如:from Cat as cat left join cat.kittens as kitten with kitten.bodyWeight > 10.0
fetch
可以要求立即返回關聯的集合對象,如:

java代碼:
1. from Cat as cat 
2. inner join fetch cat.mate 
3. left join fetch cat.kittens 
對於沒有關聯的實體,如何使用join呢?
對於沒有關聯的實體,想要使用join,可以采用本地查詢的方式,使用sql來實現,比如:
s.createSQLQuery("select um.*,dm.* from tbl_user2 um left join tbl_dep dm on um.age=dm.uuid")
.addEntity("um",UserModel.class).addEntity("dm",DepModel.class);
1:新增
2:load、 get
3:修改
4:按條件查詢
(1)傳入條件值的方法:?或 :名稱,索引從0開始
(2)給參數賦值
(3)返回對象
(4)返回多個屬性,形成Object[]
(5)getByCondition的Hibernate版實現
5:刪除
6:分頁
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
 
也可以使用你的數據庫的Native SQL語言來查詢數據。這對你在要使用數據庫的
某些特性的時候(比如說在查詢提示或者Oracle中的 CONNECT關鍵字),這是非常有
用的。這就能夠掃清你把原來直接使用SQL/JDBC 的程序遷移到基於 Hibernate應用
的道路上的障礙。
使用SQLQuery
對原生SQL查詢執行的控制是通過SQLQuery接口進行的,通過執行
Session.createSQLQuery()獲取這個接口。下面來描述如何使用這個API進行查詢。
標量查詢(Scalar queries)
s.createSQLQuery(“select uuid,name from tbl_user”).list();
它將返回一個Object[]組成的List,Hibernate會使用ResultSetMetadata來判定返回的標量值的實際順序和類型。你也可以使用scalar來明確指明類型,如:
s.createSQLQuery("select * from tbl_user").addScalar("id", LongType. INSTANCE)
id字段就明確是long型,當然你也可以指定很多個字段的類型。
實體查詢(Entity queries)
上面的查詢都是返回標量值的,也就是從resultset中返回的“裸”數據。下面展示如何通過addEntity()讓原生查詢返回實體對象。
(1) s.createSQLQuery("select * from tbl_user").addEntity(UserModel.class);
(2) s.createSQLQuery(“select uuid,userId from tbl_user2”).addEntity (UserModel.class);  //一定要把表的所有字段羅列出來
(3) s.createSQLQuery("select {um}.uuid as {um.uuid},{um}.name as {um.name} from tbl_user {um}").addEntity("um",UserModel.class);
功能跟第二個差不多,也要把表的所有字段都羅列出來
(4)簡單點的寫法:s.createSQLQuery("select * from tbl_user2 um")
                                       .addEntity("um",UserModel.class);
(5)添加條件的示例:
s.createSQLQuery("select * from tbl_user where uuid=? and name like ?").addEntity(UserModel.class).setString(0, "3").setString(1,"%na%");
 
命名Sql查詢
可以在映射文檔中定義查詢的名字,然後就可以象調用一個命名的 HQL 查詢一樣直接調用命名 SQL查詢.在這種情況下,我們不 需要調用 addEntity() 方法。
在hbm.xml中配置,示例如下:

java代碼:
1. <sql-query name="users"> 
2. <return alias="um" class="cn.javass.h3.hello.UserModel"/> 
3. select um.name as {um.name}, 
4. um.age as {um.age}, 
5. um.uuid as {um.uuid} 
6. from tbl_user um 
7. where um.name like :name 
8. </sql-query> 
註意:因為要返回一個對象,所以要把表的所有字段都羅列上,否則會報錯“列名無效”,其實是在反射向對象賦值的時候,從sql的返回中得不到這個數據。
程序裡面調用示例:Query q = s.getNamedQuery(um.getClass().getName()
                                 +".users").setString("name", "%n%");
 
命名Sql查詢–使用return-property
使用 <return-property> 你可以明確的告訴 Hibernate 使用哪些字段別名,這取代瞭使用 {}-語法 來讓 Hibernate 註入它自己的別名。
在hbm.xml中配置,示例如下:

java代碼:
1. <sql-query name="users"> 
2. <return alias="um" class="cn.javass.h3.hello.UserModel"> 
3. <return-property name="name" column="umName"></return-property> 
4. <return-property name="uuid" column="uuid"></return-property> 
5. <return-property name="age" column="age"></return-property> 
6. </return> 
7. select um.name as umName, 
8. um.age as age, 
9. um.uuid as uuid 
10. from tbl_user um 
11. where um.name like :name 
12. </sql-query> 
具有一個直觀的、可擴展的條件查詢API是Hibernate的特色。
創建一個Criteria 實例
org.hibernate.Criteria接口表示特定持久類的一個查詢。Session是 Criteria實例的工廠。

java代碼:
查看復制到剪貼板打印
1. Criteria crit = sess.createCriteria(Cat.class); 
2. crit.setMaxResults(50); 
3. List cats = crit.list(); 
限制結果集內容
一個單獨的查詢條件是org.hibernate.criterion.Criterion 接口的一個實例org.hibernate.criterion.Restrictions類 定義瞭獲得某些內置Criterion類型的工廠方法。

java代碼:
1. List list = s.createCriteria(UserModel.class) 
2. .add(Restrictions.eq("uuid", "3")) 
3. .add(Restrictions.like("name", "%n%")) 
4. .list(); 
5. 約束可以按照邏輯分組,示例如下: 
6. List cats = sess.createCriteria(Cat.class) 
7. .add( Restrictions.like("name", "Fritz%") ) 
8. .add( Restrictions.or( 
9. Restrictions.eq( "age", new Integer(0) ), 
10. Restrictions.isNull("age") 
11.        ) 
12. ).list(); 
對結果集排序
可以使用 org.hibernate.criterion.Order 來為查詢結果排序。示例如下:

java代碼:
1. List cats = sess.createCriteria(Cat.class) 
2. .add( Restrictions.like("name", "F%") 
3. .addOrder( Order.asc("name") ) 
4. .addOrder( Order.desc("age") ) 
5. .setMaxResults(50) 
6. .list(); 
1:假如有如下程序,需要向數據庫裡面加如100000條數據:

java代碼:
查看復制到剪貼板打印
1. Session session = sessionFactory.openSession(); 
2.  Transaction tx = session.beginTransaction(); 
3.  for ( int i=0; i<100000; i++ ) { 
4.  Customer customer = new Customer(…..); 
5.  session.save(customer); 
6. } 
7. tx.commit(); 
8. session.close(); 
這個程序很顯然不能正常運行,會拋出內存溢出的例外。按照前面講過的原理,Hibernate的save方法是先把數據放到內存裡面,數據太多,導致內存溢出。
那麼該如何解決呢?
解決方案:
1:首先將 hibernate.jdbc.batch_size的批量抓取數量參數設置到一個合適值(比如,10 – 50 之間),同時最好關閉二級緩存,如果有的話。
2:批量插入,一個可行的方案如下:

java代碼:
1. for ( int i=0; i<100000; i++ ) { 
2. Customer customer = new Customer(…..); 
3. session.save(customer); 
4. if ( i % 20 == 0 ) { 
5. //將本批數據插入數據庫,並釋放內存 
6. session.flush(); 
7. session.clear(); 
8. } 
9. } 
批量更新的做法跟這個類似
1:默認的Hibernate是有緩存的,稱之為一級緩存。
2:也可以使用StatelessSession,來表示不實現一級緩存,也不和二級緩存和查詢緩存交互
3:StatelessSession是低層的抽象,和底層JDBC相當接近

java代碼:
1. StatelessSession session = sessionFactory.openStatelessSession(); 
2. Transaction tx = session.beginTransaction(); 
3. ScrollableResults customers = session.getNamedQuery("GetCustomers") .scroll(ScrollMode.FORWARD_ONLY); 
4. while ( customers.next() ) { 
5. Customer customer = (Customer) customers.get(0); 
6. customer.updateStuff(…); 
7. session.update(customer); 
8. } tx.commit(); session.close(); 
9.   

 作者:jinnianshilongnian

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。