Hibernate學習大全

xiangzhihong8發表於2016-05-30

第1課 課程內容. 6

第2課Hibernate UML圖. 6

第3課 風格. 7

第4課 資源. 7

第5課 環境準備. 7

第6課 第一個示例HibernateHelloWorld 7

第7課 建立Annotation版本的HellWorld 9

第8課 什麼是O/RMapping 11

一、     定義:. 11

二、     Hibernate的創始人:. 11

三、     Hibernate做什麼:. 12

四、     Hibernate存在的原因:. 12

五、     Hibernate的優缺點:. 12

六、     Hibernate使用範圍:. 12

第9課Hibernate的重點學習:Hibernate的物件關係對映. 12

一、物件---關係對映模式. 12

二、常用的O/R對映框架:. 13

第10課 模擬Hibernate原理(OR模擬) 13

一、     專案名稱. 13

二、     原始碼. 13

第11課Hibernate基礎配置. 15

一、     提綱. 15

二、     介紹MYSQL的圖形化客戶端. 16

三、     Hibernate.cfg.xml:hbm2ddl.auto 16

四、     搭建日誌環境並配置顯示DDL語句. 16

五、     搭建Junit環境. 16

六、     ehibernate.cfg.xml : show_sql 17

七、     hibernate.cfg.xml :format_sql 17

八、     表名和類名不同,對錶名進行配置. 17

九、     欄位名和屬性相同. 17

十、     欄位名和屬性名不同. 17

十一、      不需要(持久化)psersistence的欄位. 18

十二、      對映日期與時間型別,指定時間精度. 18

十三、      對映列舉型別. 19

第12課 使用hibernate工具類將物件模型生成關係模型. 19

第13課ID主鍵生成策略. 20

一、     Xml方式. 20

<generator>元素(主鍵生成策略) 20

二、     annotateon方式. 21

1、AUTO預設. 21

2、IDENTITY 22

3、SEQUENCE 22

4、為Oracle指定定義的Sequence 22

5、TABLE - 使用表儲存id值. 23

三、     聯合主鍵. 24

1、xml方式. 24

2、annotation方式. 27

第14課Hibernate核心開發介面(重點) 29

一、     Configuration(AnnotationConfiguration) 29

二、     SessionFactory 29

三、     Session 29

1、    管理一個資料庫的任務單元. 29

2、    save(); 29

3、    delete() 29

4、    load() 29

5、    Get() 30

6、    load()與get()區別. 31

7、    update() 31

8、    saveOrUpdate() 32

9、    clear() 32

10、     flush() 33

11、     evict() 33

第15課 持久化物件的三種狀態. 35

一、     瞬時物件(TransientObject):. 35

二、     持久化物件(PersistentObject):. 35

三、     離線物件(DetachedObject):. 35

四、     三種狀態的區分:. 35

五、     總結:. 35

第16課 關係對映(重點) 36

一、     一對一 關聯對映. 36

(一)     唯一外來鍵關聯-單向(unilateralism) 37

(二)     唯一外來鍵關聯-雙向. 40

(三)     主鍵關聯-單向(不重要) 41

(四)     主鍵關聯-雙向(不重要) 44

(五)     聯合主鍵關聯(Annotation方式) 44

二、     component(元件)關聯對映. 45

(一)     Component關聯對映:. 45

(二)     User實體類:. 45

(三)     Contact值物件:. 46

(四)     xml--User對映檔案(元件對映):. 46

(五)     annotateon註解. 46

(六)     匯出資料庫輸出SQL語句:. 47

(七)     資料表結構:. 47

(八)     元件對映資料儲存:. 47

三、     多對一– 單向. 48

(一)     物件模型圖:. 48

(二)     關係模型:. 48

(三)     關聯對映的本質:. 48

(四)     實體類. 48

(五)     xml方式:對映檔案:. 49

(六)     annotation 50

(七)     多對一 儲存(先儲存group(物件持久化狀態後,再儲存user)):. 50

(八)     重要屬性-cascade(級聯):. 51

(九)     多對一  載入資料. 51

四、     一對多- 單向. 51

(一)     物件模型:. 52

(二)     關係模型:. 52

(三)     多對一、一對多的區別:. 52

(四)     實體類. 52

(五)     xml方式:對映. 52

(六)     annotateon註解. 53

(七)     匯出至資料庫(hbmàddl)生成的SQL語句:. 53

(八)     一對多 單向儲存例項:. 53

(九)     生成的SQL語句:. 54

(十)     一對多,在一的一端維護關係的缺點:. 54

(十一)    一對多 單向資料載入:. 54

(十二)    載入生成SQL語句:. 54

五、     一對多- 雙向. 54

(一)     xml方式:對映. 55

(二)     annotateon方式註解. 55

(三)     資料儲存:. 56

(四)     關於inverse屬性:. 56

(五)     Inverse和cascade區別:. 56

(六)     一對多雙向關聯對映總結:. 57

六、     多對多- 單向. 57

(一)     例項場景:. 57

(二)     物件模型:. 57

(三)     關係模型:. 57

(四)     實體類. 57

(五)     xml方式:對映. 58

(六)     annotation註解方式. 58

(七)     生成SQL語句. 59

(八)     資料庫表及結構:. 59

(九)     多對多關聯對映 單向資料儲存:. 59

(十)     多對多關聯對映 單向資料載入:. 61

七、     多對多- 雙向. 61

(一)     xml方式:對映. 61

(二)     annotation註解方式. 62

八、     關聯關係中的CRUD_Cascade_Fetch 63

九、     集合對映. 63

十、     繼承關聯對映. 64

(一)     繼承關聯對映的分類:. 64

(二)     物件模型:. 64

(三)     單表繼承SINGLE_TABLE:. 64

(四)     具體表繼承JOINED:. 70

(五)     類表繼承TABLE_PER_CLASS 72

(六)     三種繼承關聯對映的區別:. 74

第17課hibernate樹形結構(重點) 75

一、     節點實體類:. 75

二、     xml方式:對映檔案:. 75

三、     annotation註解. 76

四、     測試程式碼:. 76

五、     相應的類程式碼:. 76

第18課 作業-學生、課程、分數的對映關係. 79

一、     設計. 79

二、     程式碼:. 79

三、     注意. 80

第19課Hibernate查詢(Query Language) 80

一、     Hibernate可以使用的查詢語言. 80

二、     例項一. 80

三、     實體一測試程式碼:. 82

四、     例項二. 86

五、     例項二測試程式碼. 87

第20課Query by Criteria(QBC) 89

一、     實體程式碼:. 89

二、     Restrictions用法. 90

三、     工具類Order提供設定排序方式. 91

四、     工具類Projections提供對查詢結果進行統計與分組操作. 91

五、     QBC分頁查詢. 92

六、     QBC複合查詢. 92

七、     QBC離線查詢. 92

第21課Query By Example(QBE) 92

一、     例項程式碼. 92

第22課Query.list與query.iterate(不太重要) 93

一、     query.iterate查詢資料. 93

二、     query.list()和query.iterate()的區別. 94

三、     兩次query.list() 94

第23課 效能優化策略. 95

第24課hibernate快取. 95

一、     Session級快取(一級快取) 95

二、     二級快取. 95

1、    二級快取的配置和使用:. 96

2、    二級快取的開啟:. 96

3、    指定二級快取產品提供商:. 96

4、    使用二級快取. 97

5、    應用範圍. 99

6、    二級快取的管理:. 99

7、    二級快取的互動. 100

8、    總結. 102

三、     查詢快取. 102

四、     快取演算法. 103

第25課 事務併發處理. 104

一、     資料庫的隔離級別:併發性作用。. 104

1、    Mysql檢視資料庫隔離級別:. 104

2、    Mysql資料庫修改隔離級別:. 104

二、     事務概念(ACID) 104

三、     事務併發時可能出現問題. 104

第26課hibernate悲觀鎖、樂觀鎖. 105

一、     悲觀鎖. 105

1、    悲觀鎖的實現. 105

2、    悲觀鎖的適用場景:. 105

3、    例項:. 105

4、    悲觀鎖的使用. 106

5、    執行輸出SQL語句:. 106

二、     樂觀鎖. 107


第1課 課程內容

1、  HelloWorld

a)        Xml

b)        Annotction

2、  Hibernate原理模擬-什麼是O/RMapping以及為什麼要有O/RMapping

3、  常風的O/R框架

4、  Hibernate基礎配置

5、  Hibernate核心介面介紹

6、  物件的三種狀態

7、  ID生成策略

8、  關係對映

9、  Hibernate查詢(HQL)

10、    在Struts基礎上繼續完美BBS2009

11、    效能優化

12、    補充話題


第2課 Hibernate UML圖

第3課 風格

1、  先脈絡,後細節

2、  先操作、後原理

3、  重Annotation,輕xml配置檔案

a)        JPA (可以認為EJB3的一部分)

b)        Hibernate– extension

第4課 資源

1、  http://www.hibernate.org

a)        hibernate-distribution-3.3.2.GA-dist.zip

b)        hibernate-annotations-3.4.0.GA.zip

c)         slf4j-1.5.10.zip    (hibernate內部日誌用)

2、  hibernatezh_CN文件

3、  hibernateannotateon references

第5課 環境準備

1、  下載hibernate3.3.2

2、  下載hibernate-annotations-3.4.0

3、  注意閱讀hibernate compatibility matrix

4、  下載slf4j 1.5.8

第6課 第一個示例Hibernate HelloWorld

1、  建立新的java專案,名為hibernate_0100_HelloWorld

2、  學習建立User-liberary-hibernate,並加入相應的jar包

a)        專案右鍵-build path-configure build path-add library

b)        選擇User-library ,在其中新建library,命名為hibernate

c)         在該library中加入hibernate所需的jar名

                        i.             Hibernatecore

                       ii.             /lib/required

                     iii.             Slf-nopjar

3、  引入MySQL的JDBC驅動名

4、  在mysql中建立對應的資料庫以及表

a)        Create databasehibernate;

b)        Usehibernate;

c)         Createtable Student (id int primary key, name varchar(20),age int);

5、  建立hibernate配置檔案hibernate.cfg.xml

a)        從參考文件中copy

b)        修改對應的資料庫連線

c)         註釋提暫時不需要的內容

6、  建立Student類

7、  建立Student對映檔案Student.hbm.xml

a)        參考文件

8、  將對映檔案加入到hibernate.cfg.xml

a)        參考文件

9、  寫測試類Main,在Main中對Student物件進行直接的儲存測試

public staticvoid main(String[] args) { 
         
        Configuration cfg = null; 
        SessionFactory sf = null; 
        Session session = null; 
         
        Student s = new Student(); 
        s.setId(2); 
        s.setName("s1"); 
        s.setAge(1); 
         
        /*
         * org.hibernate.cfg.Configuration類的作用:
         * 讀取hibernate配置檔案(hibernate.cfg.xml或hiberante.properties)的.
         * new Configuration()預設是讀取hibernate.properties
         * 所以使用new Configuration().configure();來讀取hibernate.cfg.xml配置檔案
         */ 
        cfg = new Configuration().configure(); 
         
        /*
         * 建立SessionFactory
         * 一個資料庫對應一個SessionFactory
         * SessionFactory是線執行緒安全的。
         */ 
        sf = cfg.buildSessionFactory(); 
         
        try { 
            //建立session 
            //此處的session並不是web中的session 
            //session只有在用時,才建立concation,session還管理快取。 
            //session用完後,必須關閉。 
            //session是非執行緒安全,一般是一個請求一個session. 
            session = sf.openSession(); 
             
            //手動開啟事務(可以在hibernate.cfg.xml配置檔案中配置自動開啟事務) 
            session.beginTransaction(); 
            /*
             * 儲存資料,此處的資料是儲存物件,這就是hibernate操作物件的好處,
             * 我們不用寫那麼多的JDBC程式碼,只要利用session操作物件,至於hibernat如何存在物件,這不需要我們去關心它,
             * 這些都有hibernate來完成。我們只要將物件建立完後,交給hibernate就可以了。
             */ 
            session.save(s); 
            session.getTransaction().commit(); 
        } catch (HibernateException e) { 
            e.printStackTrace(); 
            //回滾事務 
            session.getTransaction().rollback(); 
        } finally { 
            //關閉session 
public static void main(String[] args) {  
          
        Configuration cfg = null;  
        SessionFactory sf = null;  
        Session session = null;  
          
        Student s = new Student();  
        s.setId(2);  
        s.setName("s1");  
        s.setAge(1);  
          
        /* 
         * org.hibernate.cfg.Configuration類的作用: 
         * 讀取hibernate配置檔案(hibernate.cfg.xml或hiberante.properties)的. 
         * new Configuration()預設是讀取hibernate.properties 
         * 所以使用new Configuration().configure();來讀取hibernate.cfg.xml配置檔案 
         */  
        cfg = new Configuration().configure();  
          
        /* 
         * 建立SessionFactory 
         * 一個資料庫對應一個SessionFactory  
         * SessionFactory是線執行緒安全的。 
         */  
        sf = cfg.buildSessionFactory();  
          
        try {  
            //建立session  
            //此處的session並不是web中的session  
            //session只有在用時,才建立concation,session還管理快取。  
            //session用完後,必須關閉。  
            //session是非執行緒安全,一般是一個請求一個session.  
            session = sf.openSession();  
              
            //手動開啟事務(可以在hibernate.cfg.xml配置檔案中配置自動開啟事務)  
            session.beginTransaction();  
            /* 
             * 儲存資料,此處的資料是儲存物件,這就是hibernate操作物件的好處, 
             * 我們不用寫那麼多的JDBC程式碼,只要利用session操作物件,至於hibernat如何存在物件,這不需要我們去關心它, 
             * 這些都有hibernate來完成。我們只要將物件建立完後,交給hibernate就可以了。 
             */  
            session.save(s);  
            session.getTransaction().commit();  
        } catch (HibernateException e) {  
            e.printStackTrace();  
            //回滾事務  
            session.getTransaction().rollback();  
        } finally {  
            //關閉session  
 

1、  FAQ:

a)        要呼叫newConfiguration().configure().buildSessionFactory(),而不是省略

*       org.hibernate.cfg.Configuration類的作用:

         *讀取hibernate配置檔案(hibernate.cfg.xml或hiberante.properties)的.

         *new Configuration()預設是讀取hibernate.properties

         * 所以使用new Configuration().configure();來讀取hibernate.cfg.xml配置檔案

注意:在hibernate裡的操作都應該放在事務裡

第7課 建立Annotation版本的HellWorld

         注意:要求hibernate3.0版本以後支援

1、  建立teacher表,create table teacher(id int primary key,namevarchar(20),title varchar(10));

2、  建立Teacher類

public class Teacher { 
    private int id;  
    private String name;     
    private String title; 
    //設定主鍵使用@Id 
    public int getId() { 
        return id; 
    } 
    public void setId(int id) { 
        this.id = id; 
    } 
    public String getName() { 
        return name; 
    } 
    public void setName(String name) { 
        this.name = name; 
    } 
    public String getTitle() { 
        return title; 
    } 
    public void setTitle(String title) { 
        this.title = title; 
    } 
} 


  1. public class Teacher {  
  2.     private int id;   
  3.     private String name;      
  4.     private String title;  
  5.     //設定主鍵使用@Id  
  6.     public int getId() {  
  7.         return id;  
  8.     }  
  9.     public void setId(int id) {  
  10.         this.id = id;  
  11.     }  
  12.     public String getName() {  
  13.         return name;  
  14.     }  
  15.     public void setName(String name) {  
  16.         this.name = name;  
  17.     }  
  18.     public String getTitle() {  
  19.         return title;  
  20.     }  
  21.     public void setTitle(String title) {  
  22.         this.title = title;  
  23.     }  
  24. }  

1、  在hibernate library中加入annotation的jar包

a)        Hibernateannotations jar

b)        Ejb3persistence jar

c)         Hibernatecommon annotations jar

d)        注意文件中沒有提到hibernate-common-annotations.jar檔案

2、  參考Annotation文件建立對應的註解

  1. import javax.persistence.Entity; 
  2. import javax.persistence.Id; 
  3. /** @Entity 表示下面的這個Teacher是一個實體類
  4. * @Id 表示主鍵Id*/ 
  5. @Entity //*** 
  6. public class Teacher { 
  7.     private int id;  
  8.     private String name;     
  9.     private String title; 
  10.     //設定主鍵使用@Id 
  11.     @Id  //*** 
  12.     public int getId() { 
  13.         return id; 
  14.     } 
  15.     public void setId(int id) { 
  16.         this.id = id; 
  17.     } 
  18.     public String getName() { 
  19.         return name; 
  20.     } 
  21.     public void setName(String name) { 
  22.         this.name = name; 
  23.     } 
  24.     public String getTitle() { 
  25.         return title; 
  26.     } 
  27.     public void setTitle(String title) { 
  28.         this.title = title; 
  29.     }} 
  1. import javax.persistence.Entity;  
  2. import javax.persistence.Id;  
  3. /** @Entity 表示下面的這個Teacher是一個實體類 
  4.  * @Id 表示主鍵Id*/  
  5. @Entity //***  
  6. public class Teacher {  
  7.     private int id;   
  8.     private String name;      
  9.     private String title;  
  10.     //設定主鍵使用@Id  
  11.     @Id  //***  
  12.     public int getId() {  
  13.         return id;  
  14.     }  
  15.     public void setId(int id) {  
  16.         this.id = id;  
  17.     }  
  18.     public String getName() {  
  19.         return name;  
  20.     }  
  21.     public void setName(String name) {  
  22.         this.name = name;  
  23.     }  
  24.     public String getTitle() {  
  25.         return title;  
  26.     }  
  27.     public void setTitle(String title) {  
  28.         this.title = title;  
  29.     }}  

1、  在hibernate.cfg.xml中建立對映<maping class=…/>

<mapping class="com.wjt276.hibernate.model.Teacher"/>

注意:<mapping>標籤中使用的是class屬性,而不是resource屬性,並且使用小數點(.)導航,而不是”/”

2、  參考文件進行測試


  1. public staticvoid main(String[] args) {         
  2.         Teacher t = new Teacher(); 
  3.         t.setId(1); 
  4.         t.setName("s1"); 
  5.         t.setTitle("中級"); 
  6.         //注此處並不是使用org.hibernate.cfg.Configuration來建立Configuration 
  7. //而使用org.hibernate.cfg.AnnotationConfiguration來建立Configuration,這樣就可以使用Annotation功能 
  8.         Configuration cfg = new AnnotationConfiguration(); 
  9.          
  10.         SessionFactory sf = cfg.configure().buildSessionFactory(); 
  11.         Session session = sf.openSession(); 
  12.          
  13.         session.beginTransaction(); 
  14.         session.save(t); 
  15.         session.getTransaction().commit(); 
  16.          
  17.         session.close(); 
  18.         sf.close(); 
  19.     } 
  1. public static void main(String[] args) {          
  2.         Teacher t = new Teacher();  
  3.         t.setId(1);  
  4.         t.setName("s1");  
  5.         t.setTitle("中級");  
  6.         //注此處並不是使用org.hibernate.cfg.Configuration來建立Configuration  
  7. //而使用org.hibernate.cfg.AnnotationConfiguration來建立Configuration,這樣就可以使用Annotation功能  
  8.         Configuration cfg = new AnnotationConfiguration();  
  9.           
  10.         SessionFactory sf = cfg.configure().buildSessionFactory();  
  11.         Session session = sf.openSession();  
  12.           
  13.         session.beginTransaction();  
  14.         session.save(t);  
  15.         session.getTransaction().commit();  
  16.           
  17.         session.close();  
  18.         sf.close();  
  19.     }  

1、  FAQ:@ 後不給提示

解決方法:windows→Proferences→seach “Content Assist”設定Auto-Activation如下:



——————————————————————————————————————————————————————————————————————————————————————————————

第8課 什麼是O/R Mapping

一、             定義:

ORM(ObjectRelational Mapping)---是一種為了解決物件導向與關係型資料庫存在的互不匹配的現象的技術。簡單說:ORM是通過使用描述物件和資料庫之間對映的後設資料,將Java程式中的物件自動持久化到關係資料中。本質上就是將資料從一種形式轉換到另外一種形式。


分層後,上層不需要知道下層是如何做了。

分層後,不可以迴圈依賴,一般是單向依賴。

一、             Hibernate的創始人:

Gavin King

二、             Hibernate做什麼:

1、    就是將物件模型(實體類)的東西存入關係模型中,

2、    實體中類對應關係型庫中的一個表,

3、    實體類中的一個屬性會對應關係型資料庫表中的一個列

4、    實體類的一個例項會對應關係型資料庫表中的一條記錄。

%%將物件資料儲存到資料庫、將資料庫資料讀入到物件中%%

OOA---物件導向的分析、物件導向的設計

OOD---設計物件化

OOP---物件導向的開發

阻抗不匹配---例JAVA類中有繼承關係,但關係型資料庫中不存在這個概念這就是阻抗不匹配。Hibernate可以解決這個問題

三、             Hibernate存在的原因:

1、  解決阻抗不匹配的問題;

2、  目前不存在完整的物件導向的資料庫(目前都是關係型資料庫);

3、  JDBC運算元據庫很繁瑣

4、  SQL語句編寫並不是物件導向

5、  可以在物件和關係表之間建立關聯來簡化程式設計

6、  O/RMapping簡化程式設計

7、  O/RMapping跨越資料庫平臺

8、  hibernate_0200_OR_Mapping_Simulation

四、             Hibernate的優缺點:

1、  不需要編寫的SQL語句(不需要編輯JDBC),只需要操作相應的物件就可以了,就可以能夠儲存、更新、刪除、載入物件,可以提高生產效;

2、  因為使用Hibernate只需要操作物件就可以了,所以我們的開發更物件化了;

3、  使用Hibernate,移植性好(只要使用Hibernate標準開發,更換資料庫時,只需要配置相應的配置檔案就可以了,不需要做其它任務的操作);

4、  Hibernate實現了透明持久化:當儲存一個物件時,這個物件不需要繼承Hibernate中的任何類、實現任何介面,只是個純粹的單純物件—稱為POJO物件(最純粹的物件—這個物件沒有繼承第三方框架的任何類和實現它的任何介面)

5、  Hibernate是一個沒有侵入性的框架,沒有侵入性的框架我們一般稱為輕量級框架

6、  Hibernate程式碼測試方便。

五、             Hibernate使用範圍:

1.       針對某一個物件,簡單的將它載入、編輯、修改,且修改只是對單個物件(而不是批量的進行修改),這種情況比較適用;

2.       物件之間有著很清晰的關係(例:多個使用者屬於一個組(多對一)、一個組有多個使用者(一對多));

3.       聚集性操作:批量性新增、修改時,不適合使用Hibernate(O/對映框架都不適合使用);

4.       要求使用資料庫中特定的功能時不適合使用,因為Hibernate不使用SQL語句;

第9課 Hibernate的重點學習:Hibernate的物件關係對映

一、物件---關係對映模式

l         屬性對映;

l         類對映:

l         關聯對映:

n         一對一;

n         一對多;

n         多對多。

二、常用的O/R對映框架:

1、  Hibernate

2、  ApacheOJB

3、  JDO(是SUN提出的一套標準—Java資料物件)

4、  Toplink(Orocle公司的)

5、  EJB(2.0X中有CMP;3.0X提出了一套“Java持久化API”---JPA)

6、  IBatis(非常的輕量級,對JDBC做了一個非常非常輕量級的包裝,嚴格說不是O/R對映框架,而是基於SQL的對映(提供了一套配置檔案,把SQL語句配置到檔案中,再配置一個物件進去,只要訪問配置檔案時,就可得到物件))

7、  JAP(是SUN公司的一套標準)

a)        意願統一天下

第10課 模擬Hibernate原理(OR模擬)

         我們使用一個專案來完成

         功能:有一個配置檔案,檔案中完成表名與類名物件,欄位與類屬性對應起來。

測試驅動開發

一、                      專案名稱

         hibernate_0200_OR_Mapping_Simulation

二、             原始碼

  1. Test類: 
  2.     public staticvoid main(String[] args)throws Exception{ 
  3.          
  4.         Student s = new Student(); 
  5.         s.setId(10); 
  6.         s.setName("s1"); 
  7.         s.setAge(1); 
  8.          
  9.         Session session = new Session();//此Session是我們自己定義的Session 
  10.          
  11.         session.save(s); 
  1. Test類:  
  2.     public static void main(String[] args) throws Exception{  
  3.           
  4.         Student s = new Student();  
  5.         s.setId(10);  
  6.         s.setName("s1");  
  7.         s.setAge(1);  
  8.           
  9.         Session session = new Session();//此Session是我們自己定義的Session  
  10.           
  11.         session.save(s);  
  12. }  

  1. Session類 
  2. import java.lang.reflect.Method; 
  3. import java.sql.Connection; 
  4. import java.sql.DriverManager; 
  5. import java.sql.PreparedStatement; 
  6. import java.util.HashMap; 
  7. import java.util.Map; 
  8. import com.wjt276.hibernate.model.Student; 
  9. public class Session { 
  10.     String tableName = "_Student"
  11.     Map<String,String> cfs = new HashMap<String,String>(); 
  12.     String[] methodNames;//用於存入實體類中的get方法陣列 
  13.     public Session(){ 
  14.         cfs.put("_id""id"); 
  15.         cfs.put("_name""name"); 
  16.         cfs.put("_age""age"); 
  17.         methodNames = new String[cfs.size()]; 
  18.     } 
  19.     public void save(Student s)throws Exception{ 
  20.         String sql = createSQL();//建立SQL串 
  21.         Class.forName("com.mysql.jdbc.Driver"); 
  22.         Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hibernate","root","root"); 
  23.         PreparedStatement ps = conn.prepareStatement(sql); 
  24.         // 
  25.         for(int i =0; i < methodNames.length; i++){ 
  26.             Method m = s.getClass().getMethod(methodNames[i]);//返回一個 Method 物件,它反映此 Class 物件所表示的類或介面的指定公共成員方法 
  27.             Class r = m.getReturnType();//返回一個 Class 物件,該物件描述了此 Method 物件所表示的方法的正式返回型別 
  28.             if(r.getName().equals("java.lang.String")) { 
  29.                 //對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法。 
  30.                 //個別引數被自動解包,以便與基本形參相匹配,基本引數和引用引數都隨需服從方法呼叫轉換 
  31.                 String returnValue = (String)m.invoke(s); 
  32.                 ps.setString(i + 1, returnValue); 
  33.             } 
  34.             if(r.getName().equals("int")) { 
  35.                 Integer returnValue = (Integer)m.invoke(s); 
  36.                 ps.setInt(i + 1, returnValue); 
  37.             } 
  38.             if(r.getName().equals("java.lang.String")) { 
  39.                 String returnValue = (String)m.invoke(s); 
  40.                 ps.setString(i + 1, returnValue); 
  41.             } 
  42.             System.out.println(m.getName() + "|" + r.getName()); 
  43.         }        
  44.         ps.executeUpdate(); 
  45.         ps.close(); 
  46.         conn.close(); 
  47.     } 
  48.     private String createSQL() { 
  49.         String str1 = ""
  50.         int index = 0
  51.          
  52.         for(String s : cfs.keySet()){ 
  53.             String v = cfs.get(s);//取出實體類成員屬性 
  54.             v = Character.toUpperCase(v.charAt(0)) + v.substring(1);//將成員屬性第一個字元大寫 
  55.             methodNames[index] = "get" + v;//拼實體類成員屬性的getter方法 
  56.             str1 += s + ",";//根據表中欄位名拼成欄位串 
  57.             index ++; 
  58.         } 
  59.         str1 = str1.substring(0,str1.length() -1); 
  60.         String str2 = ""
  61.         //根據表中欄位數,拼成?串 
  62.         for (int i =0; i < cfs.size(); i++){    str2 +="?,";} 
  63.         str2 = str2.substring(0,str2.length() -1); 
  64.         String sql = "insert into " + tableName +"(" + str1 +")" +" values (" + str2 +")"
  65.         System.out.println(sql); 
  66.         return sql; 
  67.     }} 
  1. Session類  
  2. import java.lang.reflect.Method;  
  3. import java.sql.Connection;  
  4. import java.sql.DriverManager;  
  5. import java.sql.PreparedStatement;  
  6. import java.util.HashMap;  
  7. import java.util.Map;  
  8. import com.wjt276.hibernate.model.Student;  
  9. public class Session {  
  10.     String tableName = "_Student";  
  11.     Map<String,String> cfs = new HashMap<String,String>();  
  12.     String[] methodNames;//用於存入實體類中的get方法陣列  
  13.     public Session(){  
  14.         cfs.put("_id""id");  
  15.         cfs.put("_name""name");  
  16.         cfs.put("_age""age");  
  17.         methodNames = new String[cfs.size()];  
  18.     }  
  19.     public void save(Student s) throws Exception{  
  20.         String sql = createSQL();//建立SQL串  
  21.         Class.forName("com.mysql.jdbc.Driver");  
  22.         Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hibernate","root","root");  
  23.         PreparedStatement ps = conn.prepareStatement(sql);  
  24.         //  
  25.         for(int i = 0; i < methodNames.length; i++){  
  26.             Method m = s.getClass().getMethod(methodNames[i]);//返回一個 Method 物件,它反映此 Class 物件所表示的類或介面的指定公共成員方法  
  27.             Class r = m.getReturnType();//返回一個 Class 物件,該物件描述了此 Method 物件所表示的方法的正式返回型別  
  28.             if(r.getName().equals("java.lang.String")) {  
  29.                 //對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法。  
  30.                 //個別引數被自動解包,以便與基本形參相匹配,基本引數和引用引數都隨需服從方法呼叫轉換  
  31.                 String returnValue = (String)m.invoke(s);  
  32.                 ps.setString(i + 1, returnValue);  
  33.             }  
  34.             if(r.getName().equals("int")) {  
  35.                 Integer returnValue = (Integer)m.invoke(s);  
  36.                 ps.setInt(i + 1, returnValue);  
  37.             }  
  38.             if(r.getName().equals("java.lang.String")) {  
  39.                 String returnValue = (String)m.invoke(s);  
  40.                 ps.setString(i + 1, returnValue);  
  41.             }  
  42.             System.out.println(m.getName() + "|" + r.getName());  
  43.         }         
  44.         ps.executeUpdate();  
  45.         ps.close();  
  46.         conn.close();  
  47.     }  
  48.     private String createSQL() {  
  49.         String str1 = "";  
  50.         int index = 0;  
  51.           
  52.         for(String s : cfs.keySet()){  
  53.             String v = cfs.get(s);//取出實體類成員屬性  
  54.             v = Character.toUpperCase(v.charAt(0)) + v.substring(1);//將成員屬性第一個字元大寫  
  55.             methodNames[index] = "get" + v;//拼實體類成員屬性的getter方法  
  56.             str1 += s + ",";//根據表中欄位名拼成欄位串  
  57.             index ++;  
  58.         }  
  59.         str1 = str1.substring(0,str1.length() -1);  
  60.         String str2 = "";  
  61.         //根據表中欄位數,拼成?串  
  62.         for (int i = 0; i < cfs.size(); i++){    str2 += "?,";}  
  63.         str2 = str2.substring(0,str2.length() -1);  
  64.         String sql = "insert into " + tableName + "(" + str1 + ")" + " values (" + str2 + ")";  
  65.         System.out.println(sql);  
  66.         return sql;  
  67.     }}  

第11課 Hibernate基礎配置

一、 提綱

1、  對應專案:hibernate_0300_BasicConfiguration

2、  介紹MYSQL的圖形化客戶端

3、  Hibernate.cfg.xml:hbm2ddl.auto

a) 先建表還是先建實體類

4、  搭建日誌環境並配置顯示DDL語句

5、  搭建Junit環境

a) 需要注意Junit的Bug

6、  ehibernate.cfg.xml: show_sql

7、  hibernate.cfg.xml:format_sql

8、  表名和類名不同,對錶名進行配置

a) Annotation:@Table

b) Xml:自己查詢

9、  欄位名和屬性相同

a) 預設為@Basic

b) Xml中不用寫column

10、    欄位名和屬性名不同

a) Annotation:@Column

b) Xml:自己查詢

11、    不需要psersistence的欄位

a) Annotation:@Transient

b) Xml:不寫

12、    對映日期與時間型別,指定時間精度

a) Annotation:@Temporal

b) Xml:指定type

13、    對映列舉型別

a) Annotation:@Enumerated

b) Xml:麻煩

14、    欄位對映的位置(field或者get方法)

a) Best practice:保持field和get/set方法的一致

15、    @Lob

16、    課外:CLOB BLOB型別的資料存取

17、    課外:Hibernate自定義資料型別

18、    Hibernate型別

二、 介紹MYSQL的圖形化客戶端

這樣的軟體網路很多,主要自己動手做

三、 Hibernate.cfg.xml:hbm2ddl.auto

在SessionFactory建立時,自動檢查資料庫結構,或者將資料庫schema的DDL匯出到資料庫. 使用 create-drop時,在顯式關閉SessionFactory時,將drop掉資料庫schema.取值 validate | update | create | create-drop

四、 搭建日誌環境並配置顯示DDL語句

我們使用slf介面,然後使用log4j的實現。

1、  首先引入log4j的jar包(log4j-1.2.14.jar),

2、  然後再引入slf4j實現LOG4J和介面卡jar包(slf4j-log4j12-1.5.8.jar)

3、  最後建立log4j的配置檔案(log4j.properties),並加以修改,只要保留

log4j.logger.org.hibernate.tool.hbm2ddl=debug

五、 搭建Junit環境

1、首先引入Junit 類庫 jar包 (junit-4.8.1.jar)

2、在專案名上右鍵→new→Source Folder→輸入名稱→finish

3、注意,你對哪個包進行測試,你就在測試下建立和那個包相同的包

4、建立測試類,需要在測試的方法前面加入”@Test”


  1. public class TeacherTest { 
  2.      
  3.     private static SessionFactory sf =null
  4.      
  5.     @BeforeClass//表示Junit此類被載入到記憶體中就執行這個方法 
  6.     public staticvoid beforClass(){ 
  7.         sf = new AnnotationConfiguration().configure().buildSessionFactory(); 
  8.     } 
  9.     @Test//表示下面的方法是測試用的。 
  10.     public void testTeacherSave(){ 
  11.         Teacher t = new Teacher(); 
  12.         t.setId(6); 
  13.         t.setName("s1"); 
  14.         t.setTitle("中級"); 
  15.         Session session = sf.openSession(); 
  16.         session.beginTransaction(); 
  17.         session.save(t); 
  18.         session.getTransaction().commit(); 
  19.          
  20.         session.close(); 
  21.     } 
  22.      
  23.     @AfterClass//Junit在類結果時,自動關閉 
  24.     public staticvoid afterClass(){ 
  25.         sf.close(); 
  26.     }} 
  1. public class TeacherTest {  
  2.       
  3.     private static SessionFactory sf = null;  
  4.       
  5.     @BeforeClass//表示Junit此類被載入到記憶體中就執行這個方法  
  6.     public static void beforClass(){  
  7.         sf = new AnnotationConfiguration().configure().buildSessionFactory();  
  8.     }  
  9.     @Test//表示下面的方法是測試用的。  
  10.     public void testTeacherSave(){  
  11.         Teacher t = new Teacher();  
  12.         t.setId(6);  
  13.         t.setName("s1");  
  14.         t.setTitle("中級");  
  15.         Session session = sf.openSession();  
  16.         session.beginTransaction();  
  17.         session.save(t);  
  18.         session.getTransaction().commit();  
  19.           
  20.         session.close();  
  21.     }  
  22.       
  23.     @AfterClass//Junit在類結果時,自動關閉  
  24.     public static void afterClass(){  
  25.         sf.close();  
  26.     }}  

六、 ehibernate.cfg.xml : show_sql

輸出所有SQL語句到控制檯. 有一個另外的選擇是把org.hibernate.SQL這個log category設為debug。

取值: true | false

七、 hibernate.cfg.xml :format_sql

在log和console中列印出更漂亮的SQL。


取值: true | false

True樣式:

  1. 16:32:39,750 DEBUG SchemaExport:377 -  
  2.     create table Teacher ( 
  3.         id integer not null
  4.         name varchar(255), 
  5.         title varchar(255), 
  6.         primary key (id) 
  1. 16:32:39,750 DEBUG SchemaExport:377 -   
  2.     create table Teacher (  
  3.         id integer not null,  
  4.         name varchar(255),  
  5.         title varchar(255),  
  6.         primary key (id)  
  7.  )  

False樣式:

  1. 16:33:40,484 DEBUG SchemaExport:377 - create table Teacher (id integer not null, name varchar(255), title varchar(255), primary key (id)) 
  1. 16:33:40,484 DEBUG SchemaExport:377 - create table Teacher (id integer not null, name varchar(255), title varchar(255), primary key (id))  

八、 表名和類名不同,對錶名進行配置


Annotation:使用 @Table(name=”tableName”) 進行註解

         例如:

  1.     /**
  2. * @Entity 表示下面的這個Teacher是一個實體類
  3. * @Table 表示對映到資料表中的表名,其中的name參數列示"表名稱"
  4. * @Id 表示主鍵Id,一般放在getXXX前面
  5. */ 
  6. @Entity 
  7. @Table(name="_teacher"
  8. public class Teacher { 
  9. [……] 
  1.     /** 
  2.  * @Entity 表示下面的這個Teacher是一個實體類 
  3.  * @Table 表示對映到資料表中的表名,其中的name參數列示"表名稱" 
  4.  * @Id 表示主鍵Id,一般放在getXXX前面 
  5.  */  
  6. @Entity  
  7. @Table(name="_teacher")  
  8. public class Teacher {  
  9. [……]  
  10. }  

Xml:

  1. <class name="Student" table="_student"
  1. <class name="Student" table="_student">  

九、 欄位名和屬性相同

Annotation:預設為@Basic

注意:如果在成員屬性沒有加入任何註解,則預設在前面加入了@Basic

Xml中不用寫column

十、 欄位名和屬性名不同

Annotation:使用@Column(name=”columnName”)進行註解

         例如:

  1. /**
  2. * @Entity 表示下面的這個Teacher是一個實體類
  3. * @Table 表示對映到資料表中的表名,其中的name參數列示"表名稱"
  4. * @Column 表示實體類成員屬性對映資料表中的欄位名,其中name引數指定一個新的欄位名
  5. * @Id 表示主鍵Id
  6. */ 
  7. @Entity 
  8. @Table(name="_teacher"
  9. public class Teacher { 
  10.  
  11.     private int id;  
  12.     private String name;     
  13.     private String title; 
  14.  
  15.     //設定主鍵使用@Id 
  16.     @Id 
  17.     public int getId() { 
  18.         return id; 
  19.     } 
  20.  
  21.     @Column(name="_name")//欄位名與屬性不同時 
  22.     public String getName() { 
  23.         return name; 
  24.     } 
  25. …… 
  1. /** 
  2.  * @Entity 表示下面的這個Teacher是一個實體類 
  3.  * @Table 表示對映到資料表中的表名,其中的name參數列示"表名稱" 
  4.  * @Column 表示實體類成員屬性對映資料表中的欄位名,其中name引數指定一個新的欄位名 
  5.  * @Id 表示主鍵Id 
  6.  */  
  7. @Entity  
  8. @Table(name="_teacher")  
  9. public class Teacher {  
  10.   
  11.     private int id;   
  12.     private String name;      
  13.     private String title;  
  14.   
  15.     //設定主鍵使用@Id  
  16.     @Id  
  17.     public int getId() {  
  18.         return id;  
  19.     }  
  20.   
  21.     @Column(name="_name")//欄位名與屬性不同時  
  22.     public String getName() {  
  23.         return name;  
  24.     }  
  25. ……  

Xml:

  1. <property name="name" column="_name"/> 
  1. <property name="name" column="_name"/>  


十一、 不需要(持久化)psersistence的欄位

就是不實體類的某個成員屬性不需要存入資料庫中

Annotation:使用@Transient 進行註解就可以了。

例如:

  1. @Transient 
  2.     public String getTitle() { 
  3.         return title; 
  1. @Transient  
  2.     public String getTitle() {  
  3.         return title;  
  4. }  

Xml:不寫(就是不需要對這個成員屬性進行對映)

十二、 對映日期與時間型別,指定時間精度

Annotation:使用@Temporal(value=TemporalType)來註解表示日期和時間的註解

            其中TemporalType有三個值:TemporalType.TIMESTAMP 表示yyyy-MM-dd HH:mm:ss

                                     TemporalType.DATE      表示yyyy-MM-dd

                                     TemporalType.TIME      表示HH:mm:ss


  1. @Temporal(value=TemporalType.DATE) 
  2.     public Date getBirthDate() { 
  3.         return birthDate; 
  1. @Temporal(value=TemporalType.DATE)  
  2.     public Date getBirthDate() {  
  3.         return birthDate;  
  4. }  

注意:當使用註解時,屬性為value時,則這個屬性名可以省略,例如:@Temporal(TemporalType)

Xml:使用type屬性指定hibernate型別

  1. <property name="birthDate" type="date"/> 
  1. <property name="birthDate" type="date"/>  

    注意:hibernate日期時間型別有:date, time, timestamp,當然您也可以使用Java包裝類

        

十二、 對映列舉型別

Annotation:使用@Enumerated(value=EnumType)來註解表示此成員屬性為列舉對映到資料庫

        其中EnumType有二個值:①EnumType.STRING  表示直接將列舉名稱存入資料庫

                             ②EnumType.ORDINAL 表示將列舉所對應的數值存入資料庫

Xml:對映非常的麻煩,先要定義自定義型別,然後再使用這個定義的型別……

第12課 使用hibernate工具類將物件模型生成關係模型

(也就是實體類生成資料庫中的表),完整程式碼如下:

package com.wjt276.hibernate;

import org.hibernate.cfg.AnnotationConfiguration;

import org.hibernate.cfg.Configuration;

import org.hibernate.tool.hbm2ddl.SchemaExport;

/**

* Hibernate工具<br/>

* 將物件模型生成關係模型(將物件生成資料庫中的表)

* 把hbm對映檔案(或Annotation註解)轉換成DDL

* 生成資料表之前要求已經存在資料庫

* 注:這個工具類建立好後,以後就不用建立了。以後直接Copy來用。

@author wjt276

@version 1.0 2009/10/16

*/

public classExportDB {

    public static voidmain(String[] args){

        /*org.hibernate.cfg.Configuration類的作用:

         *讀取hibernate配置檔案(hibernate.cfg.xml或hiberante.properties)的.

         *new Configuration()預設是讀取hibernate.properties

         *所以使用newConfiguration().configure();來讀取hibernate.cfg.xml配置檔案

         */

        Configuration cfg = new AnnotationConfiguration().configure();

        /*org.hibernate.tool.hbm2ddl.SchemaExport工具類:

         *需要傳入Configuration引數

         *此工具類可以將類匯出生成資料庫表

         */

        SchemaExport export = newSchemaExport(cfg);

        /** 開始匯出

         *第一個引數:script是否列印DDL資訊

         *第二個引數:export是否匯出到資料庫中生成表

         */

        export.create(truetrue);

    }}

執行剛剛建立的ExportDB類中的main()方法,進行實際的匯出類。

第13課 ID主鍵生成策略

一、 Xml方式

<id>標籤必須配置在<class>標籤內第一個位置。由一個欄位構成主鍵,如果是複雜主鍵<composite-id>標籤

被對映的類必須定義對應資料庫表主鍵欄位。大多數類有一個JavaBeans風格的屬性, 為每一個例項包含唯一的標識。<id> 元素定義了該屬性到資料庫表主鍵欄位的對映。

<id

       name="propertyName"                                          (1)

       type="typename"                                              (2)

       column="column_name"                                         (3)

       unsaved-value="null|any|none|undefined|id_value"             (4)

       access="field|property|ClassName"                            (5)

       node="element-name|@attribute-name|element/@attribute|.">

        <generatorclass="generatorClass"/>

</id>

(1) name (可選): 標識屬性的名字(實體類的屬性)。

(2) type (可選): 標識Hibernate型別的名字(省略則使用hibernate預設型別),也可以自己配置其它hbernate型別(integer, long, short, float,double, character, byte, boolean, yes_no, true_false)

(2) length(可選):當type為varchar時,設定欄位長度

(3) column (可選 - 預設為屬性名): 主鍵欄位的名字(省略則取name為欄位名)。

(4) unsaved-value (可選 - 預設為一個切合實際(sensible)的值): 一個特定的標識屬性值,用來標誌該例項是剛剛建立的,尚未儲存。 這可以把這種例項和從以前的session中裝載過(可能又做過修改--譯者注) 但未再次持久化的例項區分開來。

(5) access (可選 - 預設為property): Hibernate用來訪問屬性值的策略。

如果 name屬性不存在,會認為這個類沒有標識屬性。

unsaved-value 屬性在Hibernate3中幾乎不再需要。

還有一個另外的<composite-id>定義可以訪問舊式的多主鍵資料。 我們強烈不建議使用這種方式。

<generator>元素(主鍵生成策略)

主鍵生成策略是必須配置

用來為該持久化類的例項生成唯一的標識。如果這個生成器例項需要某些配置值或者初始化引數, 用<param>元素來傳遞。

<id name="id"type="long" column="cat_id">

       <generator class="org.hibernate.id.TableHiLoGenerator">

               <param name="table">uid_table</param>

               <param name="column">next_hi_value_column</param>

       </generator>

</id>

所有的生成器都實現org.hibernate.id.IdentifierGenerator介面。 這是一個非常簡單的介面;某些應用程式可以選擇提供他們自己特定的實現。當然, Hibernate提供了很多內建的實現。下面是一些內建生成器的快捷名字:

increment

用於為long, short或者int型別生成 唯一標識。只有在沒有其他程式往同一張表中插入資料時才能使用。 在叢集下不要使用。

identity

對DB2,MySQL, MS SQL Server,Sybase和HypersonicSQL的內建標識欄位提供支援。 返回的識別符號是long, short 或者int型別的。 (資料庫自增)

sequence

在DB2,PostgreSQL, Oracle, SAPDB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的識別符號是long, short或者 int型別的。(資料庫自增)

hilo

使用一個高/低位演算法高效的生成long, short 或者 int型別的識別符號。給定一個表和欄位(預設分別是 hibernate_unique_key和next_hi)作為高位值的來源。 高/低位演算法生成的識別符號只在一個特定的資料庫中是唯一的。

seqhilo

使用一個高/低位演算法來高效的生成long, short 或者 int型別的識別符號,給定一個資料庫序列(sequence)的名字。

uuid

用一個128-bit的UUID演算法生成字串型別的識別符號, 這在一個網路中是唯一的(使用了IP地址)。UUID被編碼為一個32位16進位制數字的字串,它的生成是由hibernate生成,一般不會重複。

UUID包含:IP地址,JVM的啟動時間(精確到1/4秒),系統時間和一個計數器值(在JVM中唯一)。 在Java程式碼中不可能獲得MAC地址或者記憶體地址,所以這已經是我們在不使用JNI的前提下的能做的最好實現了

guid

在MS SQL Server 和 MySQL 中使用資料庫生成的GUID字串。

native

根據底層資料庫的能力選擇identity,sequence 或者hilo中的一個。(資料庫自增)

assigned

讓應用程式在save()之前為物件分配一個標示符。這是 <generator>元素沒有指定時的預設生成策略。(如果是手動分配,則需要設定此配置)

select

通過資料庫觸發器選擇一些唯一主鍵的行並返回主鍵值來分配一個主鍵。

foreign

使用另外一個相關聯的物件的識別符號。通常和<one-to-one>聯合起來使用。

二、 annotateon方式

使用@GeneratedValue(strategy=GenerationType)註解可以定義該識別符號的生成策略

Strategy有四個值:

①     、AUTO- 可以是identity column型別,或者sequence型別或者table型別,取決於不同的底層資料庫.

相當於native

②     、TABLE- 使用表儲存id值

③     、IDENTITY- identity column

④     、SEQUENCE- sequence

注意:auto是預設值,也就是說沒有後的引數則表示為auto

1、AUTO預設

@Id

    @GeneratedValue

    public int getId() {

        return id;

}

@Id

@GeneratedValue(strategy=GenerationType.AUTO)

public int getId() {

return id;

}

1、  對於mysql,使用auto_increment

2、  對於oracle使用hibernate_sequence(名稱固定)

2、IDENTITY

@Id

    @GeneratedValue(strategy=GenerationType.IDENTITY)

    public int getId() {

        return id;

}

對DB2,MySQL, MS SQL Server,Sybase和HypersonicSQL的內建標識欄位提供支援。 返回的識別符號是long, short 或者int型別的。 (資料庫自增)

注意:此生成策略不支援Oracle

3、SEQUENCE

    @Id

    @GeneratedValue(strategy=GenerationType.SEQUENCE)

    public int getId() {

        return id;

    }

在DB2,PostgreSQL,Oracle, SAP DB, McKoi中使用序列(sequence), 而在Interbase中使用生成器(generator)。返回的識別符號是long, short或者 int型別的。(資料庫自增)

注意:此生成策略不支援MySQL

4、為Oracle指定定義的Sequence

    a)、首先需要在實體類前面申明一個Sequence如下:

        方法:@SequenceGenerator(name="SEQ_Name",sequenceName="SEQ_DB_Name")

            引數注意:SEQ_Name:表示為申明的這個Sequence指定一個名稱,以便使用

                    SEQ_DB_Name:表示為資料庫中的Sequence指定一個名稱。

                    兩個引數的名稱可以一樣。

@Entity

@SequenceGenerator(name="teacherSEQ",sequenceName="teacherSEQ_DB")

public class Teacher {

……

}

b)、然後使用@GeneratedValue註解

    方法:@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="SEQ_Name")

    引數:strategy:固定為GenerationType.SEQUENCE

         Generator:在實體類前面申明的sequnce的名稱

@Entity

@SequenceGenerator(name="teacherSEQ",sequenceName="teacherSEQ_DB")

public class Teacher {

    private int id;

   

    @Id

    @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="teacherSEQ")

    public int getId() {

        return id;

}}

5、TABLE - 使用表儲存id值

  原理:就是在資料庫中建立一個表,這個表包含兩個欄位,一個欄位表示名稱,另一個欄位表示值。每次在新增資料時,使用第一個欄位的名稱,來取值作為新增資料的ID,然後再給這個值累加一個值再次存入資料庫,以便下次取出使用。

Table主鍵生成策略的定義:

@javax.persistence.TableGenerator(

        name="Teacher_GEN",              //生成策略的名稱

        table="GENERATOR_TABLE",     //在資料庫生成表的名稱

        pkColumnName = "pk_key",     //表中第一個欄位的欄位名 型別為varchar,key

        valueColumnName = "pk_value",    //表中第二個欄位的欄位名 int ,value

        pkColumnValue="teacher",     //這個策略中使用該記錄的第一個欄位的值(key值)

        initialValue = 1,                //這個策略中使用該記錄的第二個欄位的值(value值)初始化值

        allocationSize=1                  //每次使用資料後累加的數值

)

這樣執行後,會在資料庫建立一個表,語句如下:

create tableGENERATOR_TABLE (pk_key varchar(255),pk_value integer )

結構:


並且表建立好後,就插入了一個記錄,如下:

    

注:這條記錄的pk_value值為2,是因為剛剛做例程式時,已經插入一條記錄了。初始化時為1。

使用TABLE主鍵生成策略:

@Entity

@javax.persistence.TableGenerator(

        name="Teacher_GEN",              //生成策略的名稱

        table="GENERATOR_TABLE",     //在資料庫生成表的名稱

        pkColumnName = "pk_key",     //表中第一個欄位的欄位名 型別為varchar,key

        valueColumnName = "pk_value",    //表中第二個欄位的欄位名 int ,value

        pkColumnValue="teacher",     //這個策略中使用該記錄的第一個欄位的值(key值)

        initialValue = 1,                //這個策略中使用該記錄的第二個欄位的值(value值)初始化值

        allocationSize=1                 //每次使用資料後累加的數值

)

public class Teacher {

    private int id;

    @Id

    @GeneratedValue(strategy=GenerationType.TABLE,generator="Teacher_GEN")

    public int getId() {

        return id;}}

注意:這樣每次在新增Teacher記錄時,都會先到GENERATOR_TABLE表取pk_key=teacher的記錄後,使用pk_value值作為記錄的主鍵。然後再給這個pk_value欄位累加1,再存入到GENERATOR_TABLE表中,以便下次使用。

    這個表可以給無數的表作為主鍵表,只是新增一條記錄而以(需要保證table、pkColumnName、valueColumnName三個屬性值一樣就可以了。),這個主鍵生成策略可以跨資料庫平臺。

三、 聯合主鍵

     複合主鍵(聯合主鍵):多個欄位構成唯一性。

1、xml方式

a) 例項場景:核算期間

// 核算期間

public class FiscalYearPeriod {   

    private int fiscalYear; //核算年

    private int fiscalPeriod; //核算月  

    private Date beginDate; //開始日期  

    private Date endDate; //結束日期

    private String periodSts; //狀態

    public int getFiscalYear() {

        return fiscalYear;

    }

    public void setFiscalYear(int fiscalYear) {

        this.fiscalYear = fiscalYear;

    }

    public int getFiscalPeriod(){  return fiscalPeriod;}

    public void setFiscalPeriod(int fiscalPeriod) {

        this.fiscalPeriod =fiscalPeriod;

    }

    public DategetBeginDate() {return beginDate;}

    public void setBeginDate(DatebeginDate) {  this.beginDate = beginDate; }

    public Date getEndDate(){return endDate;}

    public void setEndDate(DateendDate) {  this.endDate = endDate; }

    public StringgetPeriodSts() {  return periodSts;}

    public voidsetPeriodSts(String periodSts) {this.periodSts = periodSts;}

}

複合主鍵的對映,一般情況把主鍵相關的屬性抽取出來單獨放入一個類中。而這個類是有要求的:必需實現序列化介面(java.io.Serializable)(可以儲存到磁碟上),為了確定這個複合主鍵類所對應物件的唯一性就會產生比較,物件比較就需要複寫物件的hashCode()、equals()方法(複寫方法如下圖片),然後在類中引用這個複合主鍵類


b) 複合主鍵類:

複合主鍵必需實現java.io.Serializable介面

public class FiscalYearPeriodPKimplements java.io.Serializable {

  private int fiscalYear;//核算年 

  private int fiscalPeriod;//核算月

  public int getFiscalYear() {

      return fiscalYear;

  }

  public void setFiscalYear(int fiscalYear) {

      this.fiscalYear = fiscalYear;

  }

  public int getFiscalPeriod(){

      return fiscalPeriod;

  }

  public void setFiscalPeriod(int fiscalPeriod) {

      this.fiscalPeriod =fiscalPeriod;

  }  

  @Override

  public int hashCode() {

      final int prime = 31;

      int result = 1;

      result = prime* result + fiscalPeriod;

      result = prime* result + fiscalYear;

      return result;

  }

  @Override

  public boolean equals(Object obj){

      if (this == obj)

          return true;

      if (obj == null)

          return false;

      if (getClass() !=obj.getClass())

          return false;

      FiscalYearPeriodPKother = (FiscalYearPeriodPK) obj;

      if (fiscalPeriod != other.fiscalPeriod)

          return false;

      if (fiscalYear != other.fiscalYear)

          return false;

      return true;

  }}

c) 實體類:(中引用了複合主鍵類)

public class FiscalYearPeriod{

    private FiscalYearPeriodPK fiscalYearPeriodPK;//引用 複合主鍵類 

    private Date beginDate;//開始日期   

    private Date endDate;//結束日期 

    private String periodSts;//狀態 

    public FiscalYearPeriodPK getFiscalYearPeriodPK() {

        return fiscalYearPeriodPK;

    }

    public void setFiscalYearPeriodPK(FiscalYearPeriodPKfiscalYearPeriodPK) {

        this.fiscalYearPeriodPK = fiscalYearPeriodPK;

    }

………………

d) FiscalYearPeriod.hbm.xml對映檔案

<hibernate-mapping>

    <class name="com.bjsxt.hibernate.FiscalYearPeriod"table="t_fiscal_year_period">

        <composite-id name="fiscalYearPeriodPK">

            <key-property name="fiscalYear"/>

            <key-property name="fiscalPeriod"/>

        </composite-id>

        <property name="beginDate"/>

        <property name="endDate"/>

        <property name="periodSts"/>

    </class>

</hibernate-mapping>

e) 匯出資料庫輸出SQL語句:

create table t_fiscalYearPeriod (fiscalYear integer not null, fiscalPeriodinteger not null, beginDate datetime, endDate datetime, periodSts varchar(255),primary key (fiscalYear, fiscalPeriod))//實體對映到資料就是兩個欄位構成複合主鍵

f) 資料庫表結構:



g) 複合主鍵關聯對映資料儲存:

    session =HibernateUtils.getSession();

    tx =session.beginTransaction();

    FiscalYearPeriod fiscalYearPeriod = new FiscalYearPeriod();

           

    //構造複合主鍵

    FiscalYearPeriodPK pk = new FiscalYearPeriodPK();

    pk.setFiscalYear(2009);

    pk.setFiscalPeriod(11);

           

    fiscalYearPeriod.setFiscalYearPeriodPK(pk);//為物件設定複合主鍵

    fiscalYearPeriod.setEndDate(new Date());

    fiscalYearPeriod.setBeginDate(new Date());

    fiscalYearPeriod.setPeriodSts("Y");

       

    session.save(fiscalYearPeriod);        

h) 執行輸出SQL語句:

Hibernate: insert into t_fiscalYearPeriod (beginDate, endDate, periodSts,fiscalYear, fiscalPeriod) values (?, ?, ?, ?, ?)

注:如果再存入相同複合主鍵的記錄,就會出錯。

i) 資料的載入:

    資料載入非常簡單,只是主鍵是一個物件而以,不是一個普通屬性。

2、annotation方式

下面是定義組合主鍵的幾種語法:

將元件類註解為@Embeddable,並將元件的屬性註解為@Id

將元件的屬性註解為@EmbeddedId

將類註解為@IdClass,並將該實體中所有屬於主鍵的屬性都註解為@Id

a) 將元件類註解為@Embeddable,並將元件的屬性註解為@Id

元件類:

@Embeddable

public class TeacherPK implementsjava.io.Serializable{

    private int id;

    private String name;

    public int getId() {return id;  }

    public void setId(int id) {this.id = id;}

    public String getName() {   return name;}

    public void setName(Stringname) {  this.name = name;}

    @Override

    public boolean equals(Object o) {       ……}

    @Override

    public int hashCode() { return this.name.hashCode();    }

}

將元件類的屬性註解為@Id,實體類中元件的引用

@Entity

public class Teacher {

  private TeacherPK pk;

  private String title;

 

  @Id

  public TeacherPK getPk(){

      return pk;

}}

b)  將元件的屬性註解為@EmbeddedId

注意:只需要在實體類中表示覆合主鍵屬性前註解為@Entity,表示此主鍵是一個複合主鍵

       注意了,複合主鍵類不需要任何的注意。

@Entity

public class Teacher {

   private TeacherPK pk;

   private String title;  

   @EmbeddedId

   public TeacherPK getPk(){

       return pk;

   }}

c)  類註解為@IdClass,主鍵的屬性都註解為@Id

需要將複合主鍵類建立好,不需要進行任何註解

在實體類中不需要進行復合主鍵類的引用

需要在實體類前面註解為@IdClass,並且指定一個value屬性,值為複合主鍵類的class

需要在實體類中進行復合主鍵成員屬性前面註解為@Id

如下:

@Entity

@IdClass(TeacherPK.class)

public class Teacher {

  //private TeacherPK pk;//不再需要

  private int id;

  private String name;   

  @Id

  public int getId() {return id;  }

  public void setId(int id) { this.id = id;   }

 

  @Id

  public String getName() {return name;}

  public void setName(Stringname) {this.name = name;

  }}

第14課 Hibernate核心開發介面(重點)

一、 Configuration(AnnotationConfiguration)

作用:進行配置資訊的管理

目標:用來產生SessionFactory

可以在configure方法中指定hibernate配置檔案,預設(不指定)時在classpath下載入hibernate.cfg.xml檔案

載入預設的hibernate的配置檔案

sessionFactory = newAnnotationConfiguration().configure().buildSessionFactory();

載入指定hibernate的配置檔案

sessionFactory=newnnotationConfiguration().configure("hibernate.xml").buildSessionFactory();

只需要關注一個方法:buildSessionFactory();

二、 SessionFactory

作用:主要用於產生Session的工廠(資料庫連線池)

當它產生一個Session時,會從資料庫連線池取出一個連線,交給這個Session

Session session = sessionFactory.getCurrentSession();

         並且可以通過這個Session取出這個連線

         關注兩個方法:

         getCurrentSession():表示當前環境沒有Session時,則建立一個,否則不用建立

         openSession():     表示建立一個Session(3.0以後不常用),使用後需要關閉這個Session

         兩方法的區別:

           ①、openSession永遠是每次都開啟一個新的Session,而getCurrentSession不是,是從上下文找、只有當前沒有Session時,才建立一個新的Session

           ②、OpenSession需要手動close,getCurrentSession不需要手動close,事務提交自動close

           ③、getCurrentSession界定事務邊界

上下文:

所指的上下文是指hibernate配置檔案(hibernate.cfg.xml)中的“current_session_context_class”所指的值:(可取值:jta|thread|managed|custom.Class)

<property name="current_session_context_class">thread</property>

         常用的是:①、thread:是從上下文找、只有當前沒有Session時,才建立一個新的Session,主要從資料界定事務

              ②、jta:主要從分散式界定事務,執行時需要Application Server來支援(Tomcat不支援)

             ③、managed:不常用

④、custom.Class:不常用

           

三、 Session

1、    管理一個資料庫的任務單元

2、    save();

session.save(Object)

session的save方法是向資料庫中儲存一個物件,這個方法產生物件的三種狀態

3、    delete() 

session.delete(Object)

   Object物件需要有ID

   物件刪除後,物件狀態為Transistent狀態

4、    load()

格式: Session.load(Class arg0,Serializable arg1) throws HibernateException

    *arg0:需要載入物件的類,例如:User.class

    *arg1:查詢條件(實現了序列化介面的物件):例"4028818a245fdd0301245fdd06380001"字串已經實現了序列化介面。如果是數值類型別,則hibernate會自動使用包裝類,例如 1

    * 此方法返回型別為Object,但返回的是代理物件。

    * 執行此方法時不會立即發出查詢SQL語句。只有在使用物件時,它才發出查詢SQL語句,載入物件。

    * 因為load方法實現了lazy(稱為延遲載入、賴載入)

    * 延遲載入:只有真正使用這個物件的時候,才載入(才發出SQL語句)

    *hibernate延遲載入實現原理是代理方式。

    * 採用load()方法載入資料,如果資料庫中沒有相應的記錄,則會丟擲異常物件不找到(org.hibernate.ObjectNotFoundException)

    try {

        session =sf.openSession();

        session.beginTransaction();

       

         User user =(User)session.load(User.class,1);

           

        //只有在使用物件時,它才發出查詢SQL語句,載入物件。

        System.out.println("user.name=" + user.getName());

           

        //因為此的user為persistent狀態,所以資料庫進行同步為龍哥。

        user.setName("發哥");

           

        session.getTransaction().commit();

    } catch (HibernateExceptione) {

        e.printStackTrace();

        session.getTransaction().rollback();

    } finally{

            if (session != null){

                if(session.isOpen()){

                    session.close();

                }

            }

    }

5、    Get()     

格式:Session.get(Class arg0,Serializable arg1)方法

* arg0:需要載入物件的類,例如:User.class

    * arg1:查詢條件(實現了序列化介面的物件):

例"4028818a245fdd0301245fdd06380001"字串已經實現了序列化介面。如果是基數型別,則hibernate會自動轉換成包裝類,如 1

    返回值: 此方法返回型別為Object,也就是物件,然後我們再強行轉換為需要載入的物件就可以了。

            如果資料不存在,則返回null;

    注:執行此方法時立即發出查詢SQL語句。載入User物件

載入資料庫中存在的資料,程式碼如下:

try {

          session =sf.openSession();

          session.beginTransaction();

                 

           * 此方法返回型別為Object,也就是物件,然後我們再強行轉換為需要載入的物件就可以了。

              如果資料不存在,則返回null

           * 執行此方法時立即發出查詢SQL語句。載入User物件。

           */

          User user = (User)session.get(User.class, 1);

         

          //資料載入完後的狀態為persistent狀態。資料將與資料庫同步。

          System.out.println("user.name=" + user.getName());

         

          //因為此的user為persistent狀態,所以資料庫進行同步為龍哥。

          user.setName("龍哥");

         

          session.getTransaction().commit();

      } catch(HibernateException e) {

          e.printStackTrace();

          session.getTransaction().rollback();

      } finally{

          if (session != null){

              if(session.isOpen()){

                  session.close();

              }

          }

6、    load()與get()區別

①、 不存在對應記錄時表現不一樣;

②、 load返回的是代理物件,等到真正使用物件的內容時才發出sql語句,這樣就要求在第一次使用物件時,要求session處於open狀態,否則出錯

③、 get直接從資料庫載入,不會延遲載入

get()和load()只根據主鍵查詢,不能根據其它欄位查詢,如果想根據非主鍵查詢,可以使用HQL

7、    update()

①        、用來更新detached物件,更新完成後轉為為persistent狀態(預設更新全部欄位)

②        更新transient物件會報錯(沒有ID)

③        更新自己設定ID的transient物件可以(預設更新全部欄位)

④        persistent狀態的物件,只要設定欄位不同的值,在session提交時,會自動更新(預設更新全部欄位)

⑤        更新部分更新的欄位(更改了哪個欄位就更新哪個欄位的內容)

a)       方法1:update/updatable屬性

xml:設定<property>標籤的update屬性,設定在更新時是否引數更新

<property name="name" update="false"/>

注意:update可取值為true(預設):參與更新;false:更新時不參與更新

annotateon:設定@Column的updatable屬性值,true參與更新,false:不參與更新

    @Column(updatable=false)

    public String getTitle(){return title;}

注意:此種方法很少用,因為它不靈活

b)       方法二:dynamic-update屬性

注意:此方法目前只適合xml方式,JAP1.0annotation沒有對應的

在實體類的對映檔案中的<class>標籤中,使用dynamic-update屬性,true:表示修改了哪個欄位就更新哪個欄位,其它欄位不更新,但要求是同一個session(不能跨session),如果跨了session同樣會更新所有的欄位內容。

<class name="com.bjsxt.Student" dynamic-update="true">

          程式碼:

@Test

  public void testUpdate5() {    

      Sessionsession = sessionFactory.getCurrentSession();

      session.beginTransaction();

      Student s =(Student)session.get(Student.class, 1);

      s.setName("zhangsan5");

      //提交時,會只更新name欄位,因為此時的s為persistent狀態

      session.getTransaction().commit();

      s.setName("z4");

      Sessionsession2 = sessionFactory.getCurrentSession();

      session2.beginTransaction();

      //更新時,會更新所有的欄位,因為此時的s不是persistent狀態

      session2.update(s);

      session2.getTransaction().commit(); }

           如果需要跨session實現更新修改的部分欄位,需要使用session.merget()方法,合併欄位內容

@Test

  public void testUpdate6() {    

      Sessionsession = sessionFactory.getCurrentSession();

      session.beginTransaction();

      Student s =(Student)session.get(Student.class, 1);

      s.setName("zhangsan6");

      session.getTransaction().commit();

      s.setName("z4");

      Sessionsession2 = sessionFactory.getCurrentSession();

      session2.beginTransaction();

      session2.merge(s);

      session2.getTransaction().commit()}

這樣雖然可以實現部分欄位更新,但這樣會多出一條select語句,因為在欄位資料合併時,需要比較欄位內容是否已變化,就需要從資料庫中取出這條記錄進行比較

c)      使用HQL(EJBQL)物件導向的查詢語言(建議)

  @Test

  public void testUpdate7() {    

      Sessionsession = sessionFactory.getCurrentSession();

      session.beginTransaction();

      Query q =session.createQuery(

"update Student s sets.name='z5' where s.id = 1");

      q.executeUpdate();

      session.getTransaction().commit();     

  }  

8、    saveOrUpdate()

在執行的時候hibernate會檢查,如果物件在資料庫中已經有對應的記錄(是指主鍵),則會更新update,否則會新增資料save

9、    clear()

清除session快取

無論是load還是get,都會首先查詢快取(一級快取,也叫session級快取),如果沒有,才會去資料庫查詢,呼叫clear()方法可以強制清除session快取

    Session session= sessionFactory.getCurrentSession();

    session.beginTransaction();

    Teacher t =(Teacher)session.load(Teacher.class, 1);

    System.out.println(t.getName());       

    session.clear();       

    Teacher t2 =(Teacher)session.load(Teacher.class, 1);

    System.out.println(t2.getName());

    session.getTransaction().commit();

注意:這樣就會發出兩條SELECT語句,如果把session.clear()去除,則只會發出一條SELECT語句,因為第二次load時,是使用session快取中ID為1的物件,而這個物件已經在第一次load到快取中 了。

10、 flush()

在hibernate中也存在flush這個功能,在預設的情況下session.commit()之前時,其實執行了一個flush命令。

Session.flush功能:

n         清理快取;

n         執行sql(確定是執行SQL語句(確定生成update、insert、delete語句等),然後執行SQL語句。)

Session在什麼情況下執行flush:

①         預設在事務提交時執行;

注意:flush時,可以自己設定,使用session.setFlushMode(FlushMode)來指定。

  session.setFlushMode(FlushMode);

FlushMode的列舉值:

l     FlushMode.ALWAYS:任務一條SQL語句,都會flush一次

l     FlushMode.AUTO  :自動flush(預設)

l     FlushMode.COMMIT: 只有在commit時才flush

l     FlushMode.MANUAL:手動flush。

l    FlushMode.NEVER :永遠不flush  此選項在效能優化時可能用,比如session取資料為只讀時用,這樣就  

不需要與資料庫同步了

        注意:設定flush模式時,需要在session開啟事務之前設定。

②        可以顯示的呼叫flush;

③        在執行查詢前,如:iterate.

注:如果主鍵生成策略是uuid等不是由資料庫生成的,則session.save()時並不會發出SQL語句,只有flush時才會發出SQL語句,但如果主鍵生成策略是native由資料庫生成的,則session.save的同時就發出SQL語句。

11、 evict()

例如:session.evict(user)

作用:從session快取(EntityEntries屬性)中逐出該物件

但是與commit同時使用,會丟擲異常

session = HibernateUtils.getSession();

tx = session.beginTransaction();

   

User1 user = new User1();

user.setName("李四");

user.setPassword("123");

user.setCreateTime(new Date());

user.setExpireTime(new Date());

           

//利用Hibernate將實體類物件儲存到資料庫中,因為user主鍵生成策略採用的是uuid,所以呼叫完成save後,只是將user納入session的管理,不會發出insert語句,但是id已經生成,session中的existsInDatabase狀態為false

session.save(user);

               

session.evict(user);//從session快取(EntityEntries屬性)中逐出該物件

//無法成功提交,因為hibernate在清理快取時,在session的臨時集合(insertions)中取出user物件進行insert操作後需要更新entityEntries屬性中的existsInDatabase為true,而我們採用evict已經將user從session中逐出了,所以找不到相關資料,無法更新,丟擲異常。

           

tx.commit();

解決在逐出session快取中的物件不丟擲異常的方法:

在session.evict()之前進行顯示的呼叫session.flush()方法就可以了。

session.save(user);

               

//flush後hibernate會清理快取,會將user物件儲存到資料庫中,將session中的insertions中的user物件清除,並且會設定session中的existsInDatabase狀態為false

session.flush();

           

session.evict(user);//從session快取(EntityEntries屬性)中逐出該物件

           

//可以成功提交,因為hibernate在清理快取時,在Session的insertions中集合中無法找到user物件所以不會發出insert語句,也不會更新session中existsInDatabase的狀態。

tx.commit();


第15課 持久化物件的三種狀態

一、 瞬時物件(Transient Object):

使用new操作符初始化的物件不是立刻就持久的。它們的狀態是瞬時的,也就是說它們沒有任何跟資料庫表相關聯的行為,只要應用不再引用這些物件(不再被任何其它物件所引用),它們的狀態將會丟失,並由垃圾回收機制回收

二、 持久化物件(Persistent Object):

持久例項是任何具有資料庫標識的例項,它有持久化管理器Session統一管理,持久例項是在事務中進行操作的----它們的狀態在事務結束時同資料庫進行同步。當事務提交時,通過執行SQL的INSERT、UPDATE和DELETE語句把記憶體中的狀態同步到資料庫中。

三、 離線物件(Detached Object):

Session關閉之後,持久化物件就變為離線物件。離線表示這個物件不能再與資料庫保持同步,它們不再受hibernate管理。


四、 三種狀態的區分:

1、  有沒有ID,(如果沒有則是Transient狀態)

2、  ID在資料庫中有沒有

3、  在記憶體裡有沒有(session快取)

五、 總結:

Transient物件:隨時可能被垃圾回收器回收(在資料庫中沒有於之對應的記錄,應為是new初始化),而執行save()方法後,就變為Persistent物件(永續性物件),沒有納入session的管理

                            記憶體中一個物件,沒有ID,快取中也沒有

Persistent物件:在資料庫有存在的對應的記錄,納入session管理。在清理快取(髒資料檢查)的時候,會和資料庫同步。

                            記憶體中有、快取中有、資料庫有(ID)

Detached物件:也可能被垃圾回收器回收掉(資料庫中存在對應的記錄,只是沒有任何物件引用它是指session引用),注引狀態經過Persistent狀態,沒有納入session的管理

記憶體有、快取沒有、資料庫有(ID)

第16課 關係對映(重點)

         注意:這裡的關係是指:物件之間的關係並不是指資料庫的關係,-----紅色重要

         存在以下關係:

1、    一對一

u       單向(主鍵、外來鍵)

u       雙向(主鍵、外來鍵)

2、    一對多

u       單向

u       雙向

3、    多對一

u       單向

u       雙向

4、    多對多

u       單向

u       雙向

5、    集合對映

u       List

u       Set

u       Map

6、    繼承關係(不重要)

u       單表

u       多表

u       一張主表、多張子表

7、    元件對映

u       @Embeddable

u       @Embedded

一、 一對一關聯對映

²        兩個物件之間是一對一的關係,如Person-IdCard(人—身份證號)

²        有兩種策略可以實現一對一的關聯對映

Ø        主鍵關聯:即讓兩個物件具有相同的主鍵值,以表明它們之間的一一對應的關係;資料庫表不會有額外的欄位來維護它們之間的關係,僅通過表的主鍵來關聯。

Ø        唯一外來鍵關聯:外來鍵關聯,本來是用於多對一的配置,但是如果加上唯一的限制之後,也可以用來表示一對一關聯關係。

物件模型

實體類:

/** 人-實體類 */

public class Person {

    private int id;

    private String name;

    public int getId() {return id;  }

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(Stringname) {this.name = name;}

}

/**身份證-實體類*/

public class IdCard {

    private int id;

    private String cardNo;

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getCardNo(){ return cardNo;}

    public void setCardNo(StringcardNo) {this.cardNo = cardNo;}

}

(一) 唯一外來鍵關聯-單向(unilateralism)

1、 說明:

人—-> 身份證號(PersonàIdCard),從IdCard看不到Person物件

2、 物件模型

需要在Person類中持有IdCard的一個引用idCard,則IdCard中沒有Person的引用


3、 關係模型

關係模型目的:是實體類對映到關係模型(資料庫中),是要求persion中新增一個外來鍵指向idcard


4、 實體類:

      注:IdCard是被引用物件,沒有變化。

         /** 人-實體類 */

public class Person {

    private int id;

    private String name;

private IdCard idCard;//引用IdCard物件

    public int getId() {return id;  }

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(String name){this.name = name;}

public IdCard getIdCard() { return idCard;}

    public void setIdCard(IdCardidCard) {this.idCard = idCard;}

}

5、 xml對映

IdCard實體類的對映檔案:

     因為IdCard是被引用的,所以沒有什麼特殊的對映

<hibernate-mapping>

  <class name="com.wjt276.hibernate.IdCard" table="t_idcard">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="cardNo"/>

  </class>

</hibernate-mapping>

Person實體類的對映檔案

         在對映時需要新增一個外來鍵的對映,就是指定IdCard的引用的對映。這樣對映到資料庫時,就會自動新增一個欄位並作用外來鍵指向被引用的表

<hibernate-mapping>

  <class name="com.wjt276.hibernate.Person" table="t_person">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name"/> 

<!-- <many-to-one>:在多的一端(當前Person一端),加入一個外來鍵(當前為idCard)指向一的一端(當前IdCard),但多對一 關聯對映欄位是可以重複的,所以需要加入一個唯一條件unique="true",這樣就可以此欄位唯一了。-->

      <many-to-one name="idCard" unique="true"/>

  </class>

</hibernate-mapping>

注意:這裡的<many-to-one>標籤中的name屬性值並不是資料庫中的欄位名,而是Person實體類中引用IdCard物件成員屬性的getxxx方法後面的xxx(此處是getIdCard,所以是idCard),要求第一個欄位小寫。如果不指定column屬性,則資料庫中的欄位名同name值

6、 annotateon註解對映

注意IdCard是被引用物件,除正常註解,無需要其它註解

/**身份證*/

@Entity

public class IdCard {

  private int id;

  private String cardNo;

  @Id

  @GeneratedValue

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getCardNo(){return cardNo;}

  public void setCardNo(StringcardNo) {this.cardNo = cardNo;}

}

而引用物件的實體類需要使用@OneToOne進行註解,來表面是一對一的關係

再使用@JoinColumn註解來為資料庫表中這個外來鍵指定個欄位名稱就可以了。如果省略@JoinColumn註解,則hibernate會自動為其生成一個欄位名(好像是:被引用物件名稱_被引用物件的主鍵ID)

/** 人-實體類 */

@Entity

public class Person {

  private int id;

  private IdCard idCard;//引用IdCard物件  

  private String name;   

  @Id

  @GeneratedValue

  public int getId() {return id;}

  @OneToOne//表示一對一的關係

  @JoinColumn(name="idCard")//為資料中的外來鍵指定個名稱

  public IdCard getIdCard(){ return idCard;}

  public String getName() {return name;}

  public void setId(int id) {this.id = id;}

  public void setIdCard(IdCardidCard) {this.idCard = idCard;}

  public void setName(Stringname) {this.name = name;}  

}

7、 生成的SQL語句:

    create tableIdCard (

        id integernot null auto_increment,

        cardNo varchar(255),

        primary key(id)

    )

    create tablePerson (

        id integernot null auto_increment,

        namevarchar(255),

        idCardinteger,//新新增的外來鍵

        primary key(id)

    )

    alter tablePerson

        add indexFK8E488775BE010483 (idCard),

        addconstraint FK8E488775BE010483

        foreign key(idCard) //外來鍵

        referencesIdCard (id)//引用IdCard的id欄位



8、 儲存測試

Session session = sf.getCurrentSession();      

IdCard idCard = new IdCard();

idCard.setCardNo("88888888888888888888888");       

session.beginTransaction();

// 如果先不儲存idCard,則出丟擲Transient異常,因為idCard不是持久化狀態。

session.save(idCard);      

Person person = new Person();

person.setName("菜10");

person.setIdCard(idCard);

session.save(person);

session.getTransaction().commit();

(二) 唯一外來鍵關聯-雙向

1、 說明:

人<—-> 身份證號(Person<->IdCard)雙向:互相持有對方的引用

2、 物件模型:



3、 關係模型:

關係模型沒有任務變化,同單向

4、 實體類:

實體類,只是相互持有物件的引用,並且要求getter和setter方法

5、 xml對映

Person實體類對映檔案:同單向的沒有變化

IdCard實體類對映檔案:如果使用同樣的方法對映,這樣就會在表中也新增一個外來鍵指向物件,但物件已經有一個外來鍵指向自己了,這樣就造成了庸欄位,因為不需要在表另外新增欄位,而是讓hibernate在載入這個物件時,會根據物件的ID到對方的表中查詢外來鍵等於這個ID的記錄,這樣就把物件載入上來了。也同樣需要使用<one-to-one>標籤來對映,但是需要使用property-ref屬性來指定物件持有你自己的引用的成員屬性名稱(是gettxxxx後面的名稱),這樣在生成資料庫表時,就不會再新增一個多於的欄位了。資料載入時hibernate會根據這些配置自己載入資料

    <class name="com.wjt276.hibernate.IdCard" table="idcard">

        <id name="id" column="id">

            <generator class="native"/></id>

        <property name="cardNo"/>

        <!--<one-to-one>標籤:告訴hibernate如何載入其關聯物件

            property-ref屬性:是根據哪個欄位進行比較載入資料 -->

        <one-to-one name="person" property-ref="idCard"/>

    </class>

一對一 唯一外來鍵 關聯對映 雙向 需要在另一端(當前IdCard),新增<one-to-one>標籤,指示hibernate如何載入其關聯物件(或引用物件),預設根據主鍵載入(載入person),外來鍵關聯對映中,因為兩個實體採用的是person的外來鍵來維護的關係,所以不能指定主鍵載入person,而要根據person的外來鍵載入,所以採用如下對映方式:

    <!--<one-to-one>標籤:告訴hibernate如何載入其關聯物件

            property-ref屬性:是根據哪個欄位進行比較載入資料 -->

        <one-to-one name="person" property-ref="idCard"/>

6、 annotateon註解對映

Person註解對映同單向一樣

IdCard註解對映如下:使用@OneToOne註解來一對一,但這樣會在表中多加一個欄位,因為需要使用物件的外來鍵來載入資料,所以使用屬性mappedBy屬性在實現這個功能

@Entity

public class IdCard {

  private int id;

  private String cardNo;

  private Person person; 

  //mappedBy:在指定當前物件在被Person物件的idCard做了對映了

  //此值:當前物件持有引用物件中引用當前物件的成員屬性名稱(getXXX後的名稱)

  //因為Person物件的持有IdCard物件的方法是getIdCard()因為需要小寫,所以為idCard

  @OneToOne(mappedBy="idCard")

  public Person getPerson(){return person;}

  public void setPerson(Person person){this.person = person;}

  @Id

  @GeneratedValue

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getCardNo(){return cardNo;}

  public void setCardNo(StringcardNo) {this.cardNo = cardNo;}

}

7、 生成SQL語句

因為關係模型沒有變化,也就是資料庫的結構沒有變化,只是在資料載入時需要相互載入對方,這由hibernate來完成。因為生成的sql語句同單向一樣

8、 儲存測試

儲存同單向一樣

9、 總結:

規律:凡是雙向關聯,必設mappedBy

(三) 主鍵關聯-單向(不重要)

主鍵關聯:即讓兩個物件具有相同的主鍵值,以表明它們之間的一一對應的關係;資料庫表不會有額外的欄位來維護它們之間的關係,僅通過表的主鍵來關聯。

1、 說明:

人—-> 身份證號(PersonàIdCard),從IdCard看不到Person物件

2、 物件模型

站在人的角度來看,物件模型與唯一外來鍵關聯一個,只是關係模型不同


3、 關係模型

因為是person引用idcard,所以idcard要求先有值。而person的主鍵值不是自己生成的。而是參考idcard的值,person表中即是主鍵,同時也是外來鍵


4、 實體類:

實體類同 一對一 唯一外來鍵關聯的實體類一個,在person物件中持有idcard物件的引用(程式碼見唯一外來鍵關係)

5、 xml對映

IdCard對映檔案,先生成ID

    <class name="com.wjt276.hibernate.IdCard" table="t_idcard">

        <id name="id"column="id">

            <generator class="native"/>

        </id>

        <property name="cardNo"/>

    </class>

Person實體類對映檔案,ID是根據IdCard主鍵值   

<class name="com.wjt276.hibernate.Person"table="t_person">

        <id name="id"column="id">

<!--因為主鍵不是自己生成的,而是作為一個外來鍵(來源於其它值),所以使用foreign生成策略

foreign:使用另外一個相關聯的物件的識別符號,通常和<one-to-one>聯合起來使用。再使用元素<param>的屬性值指定相關聯物件(這裡Person相關聯的物件為idCard,則識別符號為idCard的id)為了能夠在載入person資料同時載入IdCard資料,所以需要使用一個標籤<one-to-one>來設定這個功能。  -->

            <generator class="foreign">

                <!-- 元素<param>屬性name的值是固定為property -->

                <param name="property">idCard</param>

            </generator>

        </id>

        <property name="name"/>

        <!-- <one-to-one>標籤

        表示如何載入它的引用物件(這裡引用物件就指idCard這裡的name值是idCard),同時也說是一對一的關係。 預設方式是根據主鍵載入(把person中的主鍵取出再到IdCard中來取相關IdCard資料。) 我們也說過此主鍵也作為一個外來鍵引用 了IdCard,所以需要加一個資料庫限制(外來鍵約束)constrained="true"     -->

        <one-to-one name="idCard"constrained="true"/>

6、 annotateon註解對映

Person實體類註解

方法:只需要使用@OneToOne註解一對一關係,再使用@PrimaryKeyJoinColumn來註解主鍵關係對映。

@Entity

public class Person {

    private int id;

    private IdCard idCard;//引用IdCard物件  

    private String name;   

    @Id

    public int getId() {return id;}

    @OneToOne//表示一對一的關係

    @PrimaryKeyJoinColumn//註解主鍵關聯對映

    public IdCard getIdCard(){ return idCard;}

    public String getName() {return name;}

    public void setId(int id) {this.id = id;}

    public void setIdCard(IdCard idCard){this.idCard = idCard;}

    public void setName(Stringname) {this.name = name;}  

}

IdCard實體類,不需要持有物件的引用,正常註解就可以了。

7、 生成SQL語句

生成的兩個表並沒有多餘的欄位,因為是通過主鍵在關鍵的

    create tableIdCard (

        id integernot null auto_increment,

        cardNovarchar(255),

        primary key (id)

    )

    create tablePerson (

        id integernot null,

        namevarchar(255),

        primary key(id)

  )

alter table person

add index FK785BED805248EF3 (id),

add constraint FK785BED805248EF3

foreign key (id) references idcard (id)

    注意:annotation註解後,並沒有對映出外來鍵關鍵的關聯,而xml可以對映,是主鍵關聯不重要

8、 儲存測試

session = HibernateUtils.getSession();

tx = session.beginTransaction();

IdCard idCard = new IdCard();

idCard.setCardNo("88888888888888888888888");

           

Person person = new Person();

person.setName("菜10");

person.setIdCard(idCard);

           

//不會出現TransientObjectException異常

//因為一對一主鍵關鍵對映中,預設了cascade屬性。

session.save(person);

tx.commit();

9、 總結

讓兩個實體物件的ID保持相同,這樣可以避免多餘的欄位被建立

<id name="id"column="id">

      <!—person的主鍵來源idcard,也就是共享idCard的主鍵-->

          <generator class="foreign">

              <param name="property">idCard</param>

          </generator>

      </id>

      <property name="name"/>

<!—one-to-one標籤的含義:指示hibernate怎麼載入它的關聯物件,預設根據主鍵載入

  constrained="true",表面當前主鍵上存在一個約束:person的主鍵作為外來鍵參照了idCard-->

      <one-to-one name="idCard" constrained="true"/>

(四) 主鍵關聯-雙向(不重要)

主鍵關聯:即讓兩個物件具有相同的主鍵值,以表明它們之間的一一對應的關係;資料庫表不會有額外的欄位來維護它們之間的關係,僅通過表的主鍵來關聯。

主鍵關聯對映,實際是資料庫的儲存結構並沒有變化,只是要求雙方都可以持有物件引用,也就是說實體模型變化,實體類都相互持有對方引用。

另外對映檔案也變化了。


1、 xml對映

Person實體類對映檔案不變,

IdCard如下:

<class name="com.wjt276.hibernate.IdCard" table="t_idcard">

        <id name="id" column="id">

            <generator class="native"/> </id>

        <property name="cardNo"/>

<!—one-to-one標籤的含義:指示hibernate怎麼載入它的關聯物件(這裡的關聯物件為person),預設根據主鍵載入-->

        <one-to-one name="person"/>

    </class>

2、 annotateon註解對映:

Person的註解不變,同主鍵單向註解

IdCard註解,只需要在持有物件引用的getXXX前加上

        @OneToOne(mappedBy="idCard") 如下:

@Entity

public class IdCard {

    private int id;

    private String cardNo;

    private Person person; 

    @OneToOne(mappedBy="idCard")

    public Person getPerson(){

        return person;

    }}

(五) 聯合主鍵關聯(Annotation方式)

實現上聯合主鍵的原理同唯一外來鍵關聯-單向一樣,只是使用的是@JoinColumns,而不是@JoinColumn,實體類註解如下:

    @OneToOne

    @JoinColumns(

        {

            @JoinColumn(name="wifeId", referencedColumnName="id"),

            @JoinColumn(name="wifeName", referencedColumnName="name")

        }

    )

    public WifegetWife() {

        return wife;

    }

        注意:@oinColumns註解聯合主鍵一對一聯絡,然後再使用@JoinColumn來註解當前表中的外來鍵欄位名,並指定關聯哪個欄位,使用referencedColumnName指定哪個欄位的名稱

二、 component(元件)關聯對映

(一) Component關聯對映:

目前有兩個類如下:


         大家發現使用者與員工存在很多相同的欄位,但是兩者有不可以是同一個類中,這樣在實體類中每次都要輸入很多資訊,現在把聯絡資訊抽取出來成為一個類,然後在使用者、員工物件中引用就可以,如下:


值物件沒有標識,而實體物件具有標識,值物件屬於某一個實體,使用它重複使用率提升,而且更清析。

以上關係的對映稱為component(元件)關聯對映

在hibernate中,component是某個實體的邏輯組成部分,它與實體的根本區別是沒有oid,component可以成為是值物件(DDD)。

採用component對映的好處:它實現了物件模型的細粒度劃分,層次會更加分明,複用率會更高。

(二) User實體類:

public class User {

private int id;

  private String name;   

  private Contact contact;//值物件的引用   

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getName() {   return name;}

  public void setName(Stringname) {  this.name = name;}

  public ContactgetContact() {   return contact;}

  public void setContact(Contactcontact) {   this.contact = contact;}   

}

(三) Contact值物件:

public class Contact {

  private String email;  

  private String address;

  private String zipCode;

  private String contactTel;

  public String getEmail(){  return email;}

  public void setEmail(Stringemail) {    this.email = email; }

  public StringgetAddress() {return address;}

  public void setAddress(Stringaddress) {this.address = address;}

  public StringgetZipCode() {return zipCode;}

  public void setZipCode(StringzipCode) {this.zipCode = zipCode;}

  public StringgetContactTel() { return contactTel;}

  public voidsetContactTel(String contactTel){this.contactTel = contactTel;}

}

(四) xml--User對映檔案(元件對映):

<hibernate-mapping>

  <class name="com.wjt276.hibernate.User" table="t_user">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name" column="name"/>

      <!-- <component>標籤用於對映Component(元件)關係

          其內部屬性正常對映。

       -->

      <component name="contact">

          <property name="email"/>

          <property name="address"/>

          <property name="zipCode"/>

          <property name="contactTel"/>          

      </component>

  </class>

</hibernate-mapping>

(五) annotateon註解

使用@Embedded用於註解元件對映,表示嵌入物件的對映

@Entity

public class User {

    private int id;

    private String name;   

    private Contact contact;//值物件的引用   

    @Id

    @GeneratedValue

    public int getId() {    return id;}

    @Embedded//用於註解元件對映,表示嵌入物件的對映

    public ContactgetContact() {return contact;}

    public void setContact(Contactcontact) {this.contact = contact;} 

Contact類是值物件,不是實體物件,是屬於實體類的某一部分,因此沒有對映檔案

(六) 匯出資料庫輸出SQL語句:

    create table User (

        id integer not null auto_increment,

        address varchar(255),

        contactTel varchar(255),

        email varchar(255),

        zipCode varchar(255),

        name varchar(255),

        primary key (id)

)

(七) 資料表結構:


注:雖然實體類沒有基本聯絡資訊,只是有一個引用,但在對映資料庫時全部都對映進來了。以後值物件可以重複使用,只要在相應的實體類中加入一個引用即可。

(八) 元件對映資料儲存:

           session =HibernateUtils.getSession();

           tx =session.beginTransaction();

           User user= new User();

           user.setName("10");

          

           Contactcontact = new Contact();

           contact.setEmail("wjt276");

           contact.setAddress("aksdfj");

           contact.setZipCode("230051");

           contact.setContactTel("3464661");

          

           user.setContact(contact);

           session.save(user);

          

           tx.commit();

實體類中引用值物件時,不用先儲存值物件,因為它不是實體類,它只是一個附屬類,而session.save()中儲存的物件是實體類。

三、       多對一 –單向

場景:使用者和組;從使用者角度來,多個使用者屬於一個組(多對一 關聯)

使用hibernate開發的思路:先建立物件模型(領域模型),把實體抽取出來。

目前兩個實體:使用者和組兩個實體,多個使用者屬於一個組,那麼一個使用者都會對應於一個組,所以使用者實體中應該有一個持有組的引用。

(一) 物件模型圖:



(二) 關係模型:

 
 


(三) 關聯對映的本質:

    將關聯關係對映到資料庫,所謂的關聯關係是物件模型在記憶體中一個或多個引用。

(四) 實體類

User實體類:

public class User {

  private int id;

  private String name;

  private Group group;

  public Group getGroup() {return group;  }

public void setGroup(Group group) {this.group = group;}

  public int getId() {return id;  }

  public void setId(int id) { this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {  this.name = name;}}

Group實體類:

public class Group {

  private int id;

  private String name;

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {this.name = name;}

}

實體類建立完後,開始建立對映檔案,先建立簡單的對映檔案:

(五) xml方式:對映檔案:

1、 Group實體類的對映檔案:

<hibernate-mapping>

  <class name="com.wjt276.hibernate.Group" table="t_group">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name"/>

  </class>

</hibernate-mapping>

2、 User實體類的對映檔案:

<hibernate-mapping>

  <class name="com.wjt276.hibernate.User" table="t_user">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name"/>

      <!--<many-to-one> 關聯對映 多對一的關係

  name:是維護的屬性(User.group),這樣表示在多的一端表裡加入一個欄位名稱為group,

但group與SQL中的關鍵字重複,所以需要重新命名欄位(column="groupid").這樣這個欄位(groupid)會作為外來鍵參照資料庫中group表(t_group也叫一的一端),也就是就在多的一 端加入一個外來鍵指向一的一端。 -->

      <many-to-one name="group" column="groupid"/>

  </class>

</hibernate-mapping>

3、 ※<many-to-one>標籤※:

例如:<many-to-one name="group" column="groupid"/>

<many-to-one> 關聯對映 多對一的關係

name:是維護的屬性(User.group),這樣表示在多的一端表裡加入一個欄位名稱為group,但group與SQL中的關鍵字重複,所以需要重新命名欄位(column="groupid").這樣這個欄位(groupid)會作為外來鍵參照資料庫中group表(t_group也叫一的一端),也就是就在多的一端加入一個外來鍵指向一的一端。

         這樣匯出至資料庫會生成下列語句:

alter table t_user drop foreign keyFKCB63CCB695B3B5AC

drop table if exists t_group

drop table if exists t_user

create table t_group (id integer not nullauto_increment, name varchar(255), primary key (id))

create table t_user (id integer not nullauto_increment, name varchar(255), groupid integer,primary key (id))

alter table t_user add index FKCB63CCB695B3B5AC (groupid), add constraint FKCB63CCB695B3B5AC foreign key (groupid) referencest_group (id)

(六) annotation

Group(一的一端)註解只需要正常的註解就可以了,因為在實體類中它是被引用的。

     User*(多的一端):@ManyToOne來註解多一對的關鍵,並且用@JoinColumn來指定外來鍵的欄位名

@Entity

public class User {

  private int id;

  private String name;

  private Group group;

  @ManyToOne

  @JoinColumn(name="groupId")

  public Group getGroup() {

      return group;

  }

(七) 多對一 儲存(先儲存group(物件持久化狀態後,再儲存user)):

session = HibernateUtils.getSession();

            tx =session.beginTransaction();

            Groupgroup = new Group();

            group.setName("wjt276");

            session.save(group); //儲存Group物件。

           

            Useruser1 = new User();

            user1.setName("菜10");

            user1.setGroup(group);//設定使用者所屬的組

           

            Useruser2 = new User();

            user2.setName("容祖兒");

            user2.setGroup(group);//設定使用者所屬的組

           

            //開始儲存

            session.save(user1);//儲存使用者

            session.save(user2);

                       

            tx.commit();//提交事務

執行後hibernate執行以下SQL語句:

Hibernate: insert into t_group (name) values (?)

Hibernate: insert into t_user (name, groupid) values (?, ?)

Hibernate: insert into t_user (name, groupid) values (?, ?)

注意:如果上面的session.save(group)不執行,則儲存不儲存不成功。則丟擲TransientObjectException異常。

因為Group為Transient狀,Object的id沒有分配值。2610644

結果:persistent狀態的物件是不能引用Transient狀態的物件

以上程式碼操作,必須首先儲存group物件,再儲存user物件。我們可以利用cascade(級聯)方式,不需要先儲存group物件。而是直接儲存user物件,這樣就可以在儲存user之前先把group儲存了。

    利用cascade屬性是解決TransientObjectException異常的一種手段。

(八) 重要屬性-cascade(級聯):

    級聯的意思是指定兩個物件之間的操作聯運關係,對一個 物件執行了操作之後,對其指定的級聯物件也需要執行相同的操作,取值:all、none、save_update、delete

1、    all:程式碼在所有的情況下都執行級聯操作

2、    none:在所有情況下都不執行級聯操作

3、    save-update:在儲存和更新的時候執行級聯操作

4、    delete:在刪除的時候執行級聯操作。

1、 xml

        例如:<many-to-one name="group"column="groupid" cascade="save-update"/>

2、 annotation

cascade屬性:其值:  CascadeType.ALL                       所有

CascadeType.MERGE                save+ update

CascadeType.PERSIST              

CascadeType.REFRESH

CascadeType.REMOVE

         例如:

                   @ManyToMany(cascade={CascadeType.ALL})

注意:cascade只是幫我們省了程式設計的麻煩而已,不要把它的作用看的太大

(九) 多對一  載入資料

程式碼如下:

session = HibernateUtils.getSession();

            tx =session.beginTransaction();

           

            Useruser = (User)session.load(User.class, 3);

            System.out.println("user.name=" + user.getName());

            System.out.println("user.group.name=" + user.getGroup().getName());

       

            //提交事務

            tx.commit();

執行後向SQL發出以下語句:

Hibernate: select user0_.id as id0_0_,user0_.name as name0_0_, user0_.groupid as groupid0_0_ from t_user user0_ whereuser0_.id=?

Hibernate: select group0_.id as id1_0_,group0_.name as name1_0_ from t_group group0_ where group0_.id=?

可以載入Group資訊:因為採用了<many-to-one>這個標籤,這個標籤會在多的一端(User)加一個外來鍵,指向一的一端(Group),也就是它維護了從多到一的這種關係,多指向一的關係。當你載入多一端的資料時,它就能把一的這一端資料載入上來。當載入User物件後hibernate會根據User物件中的groupid再來載入Group資訊給User物件中的group屬性。

四、       一對多 - 單向

在物件模型中,一對多的關聯關係,使用集合來表示。

例項場景:班級對學生;Classes(班級)和Student(學生)之間是一對多的關係。

(一) 物件模型:


(二) 關係模型:


一對多關聯對映利用了多對一關聯對映原理。

(三) 多對一、一對多的區別:

多對一關聯對映:在多的一端加入一個外來鍵指向一的一端,它維護的關係是多指向一的。

一對多關聯對映:在多的一端加入一個外來鍵指向一的一端,它維護的關係是一指向多的。

兩者使用的策略是一樣的,只是各自所站的角度不同。

(四) 實體類

Classes實體類:

public class Classes {

  private int id;

  private String name;

  //一對多通常使用Set來對映,Set是不可重複內容。

//注意使用Set這個介面,不要使用HashSet,因為hibernate有延遲載入,

  private Set<Student>students = new HashSet<Student>();

  public int getId() {return id;  }

  public void setId(int id) {this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {this.name = name;}

}

Student實體類:

public class Student {

  private int id;

  private String name;

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {  this.name = name;}

}

(五) xml方式:對映

1、 Student對映檔案:

<hibernate-mapping>

    <class name="com.wjt276.hibernate.Student" table="t_student">

        <id name="id" column="id">

            <generator class="native"/>

        </id>

        <property name="name" column="name"/>

    </class>

</hibernate-mapping>

2、 Classes對映檔案:

<hibernate-mapping>

    <class name="com.wjt276.hibernate.Classes" table="t_classess">

        <id name="id" column="id">

            <generator class="native"/>

        </id>

        <property name="name" column="name"/>     

        <!--<set>標籤 對映一對多(對映set集合),name="屬性集合名稱",然後在用<key>標籤,在多的一端加入一個外來鍵(column屬性指定列名稱)指向一的一端,再採用<one-to-many>標籤說明一對多,還指定<set>標籤中name="students"這個集合中的型別要使用完整的類路徑(例如:class="com.wjt276.hibernate.Student") -->

        <set name="students">

            <key column="classesid"/>

            <one-to-many class="com.wjt276.hibernate.Student"/>

        </set>

    </class>

</hibernate-mapping>

(六) annotateon註解

一對多 多的一端只需要正常註解就可以了。

需要在一的一端進行註解一對多的關係。

使用@OneToMany

@Entity

public class Classes {

    private int id;

    private String name;

   

    // 一對多通常使用Set來對映,Set是不可重複內容。

    // 注意使用Set這個介面,不要使用HashSet,因為hibernate有延遲載入,

    private Set<Student>students = new HashSet<Student>();

    @OneToMany//進行註解為一對多的關係

    @JoinColumn(name="classesId")//在多的一端註解一個欄位(名為classessid)

    public Set<Student>getStudents() {

        return students;

}

(七) 匯出至資料庫(hbmàddl)生成的SQL語句:

create table t_classes (id integer not null auto_increment, namevarchar(255), primary key (id))

create table t_student (id integer not null auto_increment, namevarchar(255), classesid integer, primary key (id))

alter table t_student add index FK4B90757070CFE27A (classesid), add constraint FK4B90757070CFE27A foreign key (classesid) referencest_classes (id)

(八) 一對多 單向儲存例項:

             session = HibernateUtils.getSession();

            tx =session.beginTransaction();

   

            Studentstudent1 = new Student();

            student1.setName("10");

            session.save(student1);//必需先儲存,否則在儲存classess時出錯.

           

            Studentstudent2 = new Student();

            student2.setName("祖兒");

            session.save(student2);//必需先儲存,否則在儲存classess時出錯.

           

            Set<Student>students = new HashSet<Student>();

            students.add(student1);

            students.add(student2);

           

            Classesclasses = new Classes();

            classes.setName("wjt276");

            classes.setStudents(students);

           

            session.save(classes);

            tx.commit();

(九) 生成的SQL語句:

Hibernate: insert into t_student (name) values (?)

Hibernate: insert into t_student (name) values (?)

Hibernate: insert into t_classes (name) values (?)

Hibernate: update t_student set classesid=? where id=?

Hibernate: update t_student set classesid=? where id=?

(十) 一對多,在一的一端維護關係的缺點:

    因為是在一的一端維護關係,這樣會發出多餘的更新語句,這樣在批量資料時,效率不高。

    還有一個,當在多的一端的那個外來鍵設定為非空時,則在新增多的一端資料時會發生錯誤,資料儲存不成功。

(十一)       一對多 單向資料載入:

session = HibernateUtils.getSession();

          tx =session.beginTransaction();

          Classesclasses = (Classes)session.load(Classes.class, 2);

          System.out.println("classes.name=" + classes.getName());

          Set<Student> students = classes.getStudents();

          for(Iterator<Student> iter = students.iterator();iter.hasNext();){

              Studentstudent = iter.next();

              System.out.println(student.getName());

          }

          tx.commit();

(十二)       載入生成SQL語句:

Hibernate: select classes0_.id as id0_0_, classes0_.name as name0_0_ fromt_classes classes0_ where classes0_.id=?

Hibernate: select students0_.classesid as classesid1_, students0_.id asid1_, students0_.id as id1_0_, students0_.name as name1_0_ from t_studentstudents0_ where students0_.classesid=?

五、       一對多 - 雙向

         是載入學生時,能夠把班級載入上來。當然載入班級也可以把學生載入上來

1、  在學生物件模型中,要持有班級的引用,並修改學生對映檔案就可以了。。

2、  儲存沒有變化

3、  關係模型也沒有變化

(一) xml方式:對映

學生對映檔案修改後的:

  <class name="com.wjt276.hibernate.Student" table="t_student">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name" column="name"/>

<!--使用多對一標籤對映 一對多雙向,下列的column值必需與多的一端的key欄位值一樣。-->

      <many-to-one name="classes" column="classesid"/>

  </class>

如果在一對多的對映關係中採用一的一端來維護關係的話會存在以下兩個缺點:①如果多的一端那個外來鍵設定為非空時,則多的一端就存不進資料;②會發出多於的Update語句,這樣會影響效率。所以常用對於一對多的對映關係我們在多的一端維護關係,並讓多的一端維護關係失效(見下面屬性)。

程式碼:

  <class name="com.wjt276.hibernate.Classes" table="t_classes">

      <id name="id"column="id">

          <generator class="native"/>

      </id>

      <property name="name"column="name"/>

     

      <!--

          <set>標籤 對映一對多(對映set集合),name="屬性集合名稱"

          然後在用<key>標籤,在多的一端加入一個外來鍵(column屬性指定列名稱)指向一的一端

          再採用<one-to-many>標籤說明一對多,還指定<set>標籤中name="students"這個集合中的型別

          要使用完整的類路徑(例如:class="com.wjt276.hibernate.Student")

inverse="false":一的一端維護關係失效(反轉) :false:可以從一的一端維護關係(預設);true:從一的一端維護關係失效,這樣如果在一的一端維護關係則不會發出Update語句。 -->

      <set name="students"inverse="true">

      <key column="classesid"/>

          <one-to-many class="com.wjt276.hibernate.Student"/>

      </set>

  </class>

(二) annotateon方式註解

首先需要在多的一端持有一的一端的引用

因為一對多的雙向關聯,就是多對一的關係,我們一般在多的一端來維護關係,這樣我們需要在多的一端實體類進行對映多對一,並且設定一個欄位,而在一的一端只需要進行對映一對多的關係就可以了,如下:

多的一端

@Entity

public class Student {

    private int id;

    private String name;

    private Classes classes;

   

    @ManyToOne

    @JoinColumn(name="classesId")

    public ClassesgetClasses() {

        return classes;

    }

一的一端

@Entity

public class Classes {

    private int id;

    private String name;

    // 一對多通常使用Set來對映,Set是不可重複內容。

    // 注意使用Set這個介面,不要使用HashSet,因為hibernate有延遲載入,

    private Set<Student>students = new HashSet<Student>();

    @OneToMany(mappedBy="classes")//進行註解為一對多的關係

    public Set<Student>getStudents() {

        return students;}

(三) 資料儲存:

一對多 資料儲存,從多的一端進行儲存:

session = HibernateUtils.getSession();

          tx =session.beginTransaction();

 

          Classesclasses = new Classes();

          classes.setName("wjt168");

          session.save(classes);

         

          Studentstudent1 = new Student();

          student1.setName("10");

          student1.setClasses(classes);

          session.save(student1);

         

          Studentstudent2 = new Student();

          student2.setName("祖兒");

          student2.setClasses(classes);

          session.save(student2);

         

          //提交事務

          tx.commit();

注意:一對多,從多的一端儲存資料比從一的一端儲存資料要快,因為從一的一端儲存資料時,會多更新多的一端的一個外來鍵(是指定一的一端的。)

(四) 關於inverse屬性:

inverse主要用在一對多和多對多雙向關聯上,inverse可以被設定到集合標籤<set>上,預設inverse為false,所以我們可以從一的一端和多的一端維護關聯關係,如果設定inverse為true,則我們只能從多的一端維護關聯關係。

    注意:inverse屬性,隻影響資料的儲存,也就是持久化

(五) Inverse和cascade區別:

    Inverse是關聯關係的控制方向

    Casecade操作上的連鎖反應

(六) 一對多雙向關聯對映總結:

    在一的一端的集合上使用<key>,在對方表中加入一個外來鍵指向一的一端

    在多的一端採用<many-to-one>

注意:<key>標籤指定的外來鍵欄位必須和<many-to-one>指定的外來鍵欄位一致,否則引用欄位的錯誤

如果在一的一端維護一對多的關係,hibernate會發出多餘的update語句,所以我們一般在多的一端來維護關係。

六、       多對多 - 單向

Ø        一般的設計中,多對多關聯對映,需要一箇中間表

Ø        Hibernate會自動生成中間表

Ø        Hibernate使用many-to-many標籤來表示多對多的關聯

Ø        多對多的關聯對映,在實體類中,跟一對多一樣,也是用集合來表示的。

(一) 例項場景:

使用者與他的角色(一個使用者擁有多個角色,一個角色還可以屬於多個使用者)

(二) 物件模型:



(三) 關係模型:

 
 

(四) 實體類

Role實體類:

public class Role {

    private int id;

    private String name;

    public int getId() return id;}

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(Stringname) {  this.name = name;}}

User實體類:

public class User {

    private int id;

    private String name;   

    private Set<User> roles = newHashSet<User>();// Role物件的集合

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(Stringname) {this.name = name;}

    public Set<User>getRoles() {return roles;}

    public voidsetRoles(Set<User> roles) {this.roles = roles;  }

(五) xml方式:對映

Role對映檔案:

<class name="com.wjt276.hibernate.Role" table="t_role">

        <id name="id">

            <generator class="native"/>

        </id>

        <property name="name" column="name"/>

    </class>

User對映檔案:

    <class name="com.wjt276.hibernate.User" table="t_user">

        <id name="id"column="id">

            <generator class="native"/>

        </id>

        <property name="name"/>

<!--使用<set>標籤對映集合(set),標籤中的name值為物件屬性名(集合roles),而使用table屬性是用於生成第三方表名稱,例:table="t_user_role",但是第三方面中的欄位是自動加入的,作為外來鍵分別指向其它表。

所以表<key>標籤設定,例:<key column="userid"/>,意思是:在第三方表(t_user_role)中加入一個外來鍵並且指向當前的對映實體類所對應的表(t_user).使用<many-to-many>來指定此對映集合所物件的類(例項類),並且使用column屬性加入一個外來鍵指向Role實體類所對應的表(t_role) -->

        <setname="roles" table="t_user_role">

            <key column="userid"/>

            <many-to-many class="com.wjt276.hibernate.Role" column="roleid"/>

        </set>

    </class>

(六) annotation註解方式

注意:因為是多對多單向(當然使用者擁有多個角色,一個角色也可屬性多個使用者,但這裡角色看不到使用者),所以角色只需要正常註解就可以了,

現在要使用@ManyToMany來註解多對多的關係,並使用@JoinTabel來註解第三方表的名稱,再使用joinColumns屬性來指定當前物件在第三方表中的欄位名,並且這個欄位會指向當前類相對應的表,最後再用inverseJoinColumns來指定當前類持有引用的實體類在第三方表中的欄位名,並且指向被引用物件相對應的表,如下:

@Entity

public class User {

  private int id;

  private String name;

  private Set<User> roles = new HashSet<User>();// Role物件的集合  @Id

  @GeneratedValue

  public int getId() {return id;}

 

  @ManyToMany

  @JoinTable(name="u_r",//使用@JoinTable標籤的name屬性註解第三方表名稱

          joinColumns={@JoinColumn(name="userId")},

//使用joinColumns屬性來註解當前實體類在第三方表中的欄位名稱並指向該物件

          inverseJoinColumns={@JoinColumn(name="roleId")}

//使用inverseJoinColumns屬性來註解當前實體類持有引用物件在第三方表中的欄位名稱並指向被引用物件表

  )

  public Set<User> getRoles() {return roles;}

(七) 生成SQL語句

create table t_role (id integer not null auto_increment, namevarchar(255), primary key (id))

create table t_user (id integer not null auto_increment, namevarchar(255), primary key (id))

create table t_user_role (userid integer not null, roleid integer notnull, primary key (userid, roleid))

alter table t_user_role add index FK331DEE5F1FB4B2D4 (roleid), add constraint FK331DEE5F1FB4B2D4 foreign key (roleid) referencest_role (id)

alter table t_user_role add index FK331DEE5F250A083E(userid), add constraint FK331DEE5F250A083E foreign key (userid) referencest_user (id)

注:根據DDL語句可以看出第三方表的主鍵是一個複合主鍵(primary key (userid, roleid)),也就是說記錄不可以有相同的資料。

(八) 資料庫表及結構:


(九) 多對多關聯對映 單向資料儲存:

session = HibernateUtils.getSession();

          tx =session.beginTransaction();

 

          Role r1 = new Role();

          r1.setName("資料錄入人員");

          session.save(r1);

         

          Role r2 = new Role();

          r2.setName("商務主管");

          session.save(r2);

         

          Role r3 = new Role();

          r3.setName("大區經理");

          session.save(r3);

         

          User u1 = new User();

          u1.setName("10");

          Set<Role>u1Roles = new HashSet<Role>();

          u1Roles.add(r1);

          u1Roles.add(r2);

          u1.setRoles(u1Roles);

         

          User u2 = new User();

          u2.setName("祖兒");

          Set<Role>u2Roles = new HashSet<Role>();

          u2Roles.add(r2);

          u2Roles.add(r3);

          u2.setRoles(u2Roles);

         

          User u3 = new User();

          u3.setName("成龍");

          Set<Role>u3Roles = new HashSet<Role>();

          u3Roles.add(r1);

          u3Roles.add(r2);

          u3Roles.add(r3);

          u3.setRoles(u3Roles);

         

          session.save(u1);

          session.save(u2);

          session.save(u3);

         

          tx.commit();

發出SQL語句:

Hibernate: insert into t_role (name) values (?)

Hibernate: insert into t_role (name) values (?)

Hibernate: insert into t_role (name) values (?)

Hibernate: insert into t_user (name) values (?)

Hibernate: insert into t_user (name) values (?)

Hibernate: insert into t_user (name) values (?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

Hibernate: insert into t_user_role (userid, roleid)values (?, ?)

注:前三條SQL語句,新增Role記錄,第三條到第六條新增User,最後7條SQL語句是在向第三方表(t_user_role)中新增多對多關係(User與Role關係)

(十) 多對多關聯對映 單向資料載入:

         session =HibernateUtils.getSession();

         tx =session.beginTransaction();

        

         User user =(User)session.load(User.class, 1);

         System.out.println("user.name=" + user.getName());

         for(Iterator<Role> iter = user.getRoles().iterator();iter.hasNext();){

             Rolerole = (Role) iter.next();

             System.out.println(role.getName());

         }

         tx.commit();

生成SQL語句:

Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from t_useruser0_ where user0_.id=?

user.name=10

Hibernate: select roles0_.userid as userid1_, roles0_.roleid as roleid1_,role1_.id as id2_0_, role1_.name as name2_0_ from t_user_role roles0_ leftouter join t_role role1_ on roles0_.roleid=role1_.id where roles0_.userid=?

商務主管

資料錄入人員

七、       多對多 - 雙向

    多對多關聯對映 雙向 兩方都持有物件引用,修改物件模型,但資料的儲存沒有變化。

(一)     xml方式:對映

再修改對映檔案:

    <class name="com.wjt276.hibernate.Role" table="t_role">

        <id name="id">

            <generator class="native"/>

        </id>

        <property name="name" column="name"/>

        <!—order-by 屬性是第三方表哪個欄位進行排序-->

        <set name="users" table="t_user_role"order-by="userid">

            <key column="roleid"/>

            <many-to-many class="com.wjt276.hibernate.User" column="userid"/>

        </set>  </class>

注:資料的儲存與單向一樣。但一般維護這個多對多關係,只需要使用一方,而使另一方維護關係失效。

總結:

<!— order-by 屬性是第三方表哪個欄位進行排序-->

      <set name="users" table="t_user_role"order-by="userid">

          <key column="roleid"/>

          <many-to-many class="com.wjt276.hibernate.User" column="userid"/>

      </set>

Ø        table屬性值必須和單向關聯中的table屬性值一致

Ø        <key>中column屬性值要與單向關聯中的<many-to-many>標籤中的column屬性值一致

Ø        在<many-to-many>中的column屬性值要與單向關聯中<key>標籤的column屬性值一致。

(二) annotation註解方式

多對多關聯對映 雙向 兩方都持有物件引用,修改物件模型,但資料的儲存沒有變化

只需要修改註解對映就可以了。

User實體類註解沒有變化和單向一樣:

@Entity

public class User {

  private int id;

  private Set<User> roles = new HashSet<User>();// Role物件的集合  @Id

  @GeneratedValue

  public int getId() {return id;}

  @ManyToMany

  @JoinTable(name="u_r",//使用@JoinTable標籤的name屬性註解第三方表名稱

          joinColumns={@JoinColumn(name="userId")},//使用joinColumns屬性來註解當前實體類在第三方表中的欄位名稱並指向該物件

          inverseJoinColumns={@JoinColumn(name="roleId")}

//使用inverseJoinColumns屬性來註解當前實體類持有引用物件在第三方表中的欄位名稱並指向被引用物件表

  )

  public Set<User> getRoles() {return roles;}

Role實體類註解也非常的簡單:使用@ManyToMany註解,並使用mappedBy屬性指定引用物件持有自己的的屬性名

@Entity

public class Role {

  private int id;

  private String name;

  private Set<User> users = newHashSet<User>();

  @Id

  @GeneratedValue

  public int getId() {return id;  }

  @ManyToMany(mappedBy="roles")

  public Set<User>getUsers() {return users;}

  public voidsetUsers(Set<User> users) {this.users = users;  }

多對多關聯對映 雙向 資料載入

           session =HibernateUtils.getSession();

           tx =session.beginTransaction();

          

           Role role= (Role)session.load(Role.class, 1);

           System.out.println("role.name=" + role.getName());

           for(Iterator<User> iter = role.getUsers().iterator();iter.hasNext();){

               Useruser = iter.next();

               System.out.println("user.name=" + user.getName());

           }

           tx.commit();

生成SQL語句:

Hibernate: select role0_.id as id2_0_, role0_.name as name2_0_ from t_rolerole0_ where role0_.id=?

role.name=資料錄入人員

Hibernate: select users0_.roleid as roleid1_, users0_.userid as userid1_,user1_.id as id0_0_, user1_.name as name0_0_ from t_user_role users0_ leftouter join t_user user1_ on users0_.userid=user1_.id where users0_.roleid=?order by users0_.userid

user.name=10

user.name=成龍

八、       關聯關係中的CRUD_Cascade_Fetch

1、  設定cascade可以設定在持久化時對於關聯物件的操作(CUD,R歸Fetch管)

2、  cascade僅僅是幫助我們省了程式設計的麻煩而已,不要把它的作用看的太大

a)        cascade的屬性指明做什麼操作的時候關係物件是綁在一起的

b)        refresh=A裡面需要讀B改過之後的資料

3、  鐵律:雙向關係在程式中要設定雙向關聯

4、  鐵律:雙向一定需要設定mappedBy

5、  fetch

a)        鐵律:雙向不要兩邊設定Eager(會有多餘的查詢語句發出)

b)        對多方設定fetch的時候要謹慎,結合具體應用,一般用Lazy不用eager,特殊情況(多方數量不多的可以考慮,提高效率的時候可以考慮)

6、  O/RMapping程式設計模型

a)        對映模型

                                    i.  jpa annotation

                                   ii.  hibernate annotation extension

b)        程式設計介面

                                    i.  jpa

                                   ii.  hibernate

c)         資料查詢語言

                                    i.  HQL

                                   ii.  EJBQL(JPQL)

7、  要想刪除或者更新,先做load,除了精確知道ID之外

8、  如果想消除關聯關係,先設定關係為null,再刪除對應記錄,如果不刪記錄,該記錄就變成垃圾資料

九、       集合對映

1、 Set

2、 List

a)        @OrderBy

注意:List與Set註解是一樣的,就是把Set更改為List就可以了

  private List<User>users = new ArrayList<User>();

  @OneToMany(mappedBy="group",

          cascade={CascadeType.ALL}

          )

  @OrderBy("name ASC")//使用@OrderBy註解List中使用哪個欄位進行排序,可以組合排序,中間使用逗號分開

  public List<User>getUsers() {  return users;}

  public voidsetUsers(List<User> users) {this.users = users;}

3、 Map

a)        @Mapkey

註解:關聯模型中並沒有變化,只是註解發生了變化,而這個變化是給hibernate看的

Map在對映時,因為Key是唯一的,所以一般可以使用主鍵表示,然後在對映時使用@MapKey來註解使用哪個欄位為key,如下:

private Map<Integer,User> users = new HashMap<Integer, User>();

@OneToMany(mappedBy="group", cascade=CascadeType.ALL)

@MapKey(name="id")//註解使用哪個欄位為key

public Map<Integer,User> getUsers() {  return users;}

public voidsetUsers(Map<Integer, User> users) {this.users = users;}

                   資料載入:

     Session s = sessionFactory.getCurrentSession();

     s.beginTransaction();

     Group g =(Group)s.load(Group.class, 1);

     for(Map.Entry<Integer,User> entry : g.getUsers().entrySet()) {

         System.out.println(entry.getValue().getName());

     }

     s.getTransaction().commit();

十、       繼承關聯對映

繼承對映:就是把類的繼承關係對映到資料庫裡(首先正確的儲存,再正確的載入資料)

(一) 繼承關聯對映的分類:

Ø        單表繼承:每棵類繼承樹使用一個表(table per class hierarchy)

Ø        具體表繼承:每個子類一個表(table per subclass)

Ø        類表繼承:每個具體類一個表(table per concrete class)(有一些限制)

Ø        例項環境:動物Animal有三個基本屬性,然後有一個Pig繼承了它並擴充套件了一個屬性,還有一個Brid也繼承了並且擴充套件了一個屬性

(二) 物件模型:


(三) 單表繼承SINGLE_TABLE

每棵類繼承樹使用一個表

把所有的屬性都要儲存表中,目前至少需要5個欄位,另外需要加入一個標識欄位(表示哪個具體的子類)

t_animal

Id

Name

Sex

Weight

Height

Type

1

豬豬

true

100

 

P

2

鳥鳥

false

 

50

B

其中:

           ①、id:表主鍵

           ②、name:動物的姓名,所有的動物都有

           ③、sex:動物的性別,所有的動物都有

           ④、weight:豬(Pig)的重量,只有豬才有,所以鳥鳥就沒有重量資料

           ⑤、height:鳥(height)的除錯,只有鳥才有,所以豬豬就沒有高度資料

           ⑥、type:表示動物的型別;P表示豬;B表示鳥

1、 實體類

Animal實體類:

public class Animal {

  private int id;

  private String name;   

  private boolean sex;

  public int getId() {return id;  }

  public void setId(int id) { this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {this.name = name;}

  public boolean isSex() {return sex;}

  public void setSex(boolean sex) {this.sex = sex;} 

}

Pig實體類:

public class Pig extends Animal {

  private int weight;

  public int getWeight() {return weight;}

  public void setWeight(int weight) {this.weight = weight;}

}

Bird實體類:

public class Bird extends Animal {

  private int height;

  public int getHeight() {return height;}

  public void setHeight(int height) {this.height = height;} 

}

2、 xml方式:對映

  <class name="Animal" table="t_animal"lazy="false">

      <id name="id">

          <generator class="native"/>

      </id>

      <discriminator column="type" type="string"/>

      <property name="name"/>

      <property name="sex"/>

      <subclass name="Pig" discriminator-value="P">

          <property name="weight"/>

      </subclass>

      <subclass name="Bird" discriminator-value="B">

          <property name="height"/>

      </subclass>

  </class>

1、理解如何對映

      因為類繼承樹肯定是對應多個類,要把多個類的資訊存放在一張表中,必須有某種機制來區分哪些記錄是屬於哪個類的。

  這種機制就是,在表中新增一個欄位,用這個欄位的值來進行區分。用hibernate實現這種策略的時候,有如下步驟:

  父類用普通的<class>標籤定義

  在父類中定義一個discriminator,即指定這個區分的欄位的名稱和型別

  如:<discriminator column=”XXX” type=”string”/>

  子類使用<subclass>標籤定義,在定義subclass的時候,需要注意如下幾點:

  Subclass標籤的name屬性是子類的全路徑名

  在Subclass標籤中,用discriminator-value屬性來標明本子類的discriminator欄位(用來區分不同類的欄位)

  的值Subclass標籤,既可以被class標籤所包含(這種包含關係正是表明了類之間的繼承關係),也可以與class標

  籤平行。 當subclass標籤的定義與class標籤平行的時候,需要在subclass標籤中,新增extends屬性,裡面的值

  是父類的全路徑名稱。子類的其它屬性,像普通類一樣,定義在subclass標籤的內部。

2、理解如何儲存

  儲存的時候hibernate會自動將鑑別欄位值插入到資料庫中,在載入資料的時候,hibernate能根據這個鑑別值

  正確的載入物件

 

多型查詢:在hibernate載入資料的時候能鑑別出正真的型別(instanceOf)

get支援多型查詢

load只有在lazy=false,才支援多型查詢

hql支援多型查詢

3、 annotation註解

父類中註解如下:

使用@Inheritance註解為繼承對映,再使用strategy屬性來指定繼承對映的方式

strategy有三個值:InheritanceType.SINGLE_TABLE           單表繼承

                  InheritanceType.TABLE_PER_CLASS       類表繼承

                  InheritanceType.JOINED            具體表繼承

再使用@DiscriminatorColumn注意標識欄位的欄位名,及欄位型別

在類中使用@DiscriminatorValue來註解標識欄位的值

@Entity

@Inheritance(strategy=InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(

name="discriminator",

discriminatorType=DiscriminatorType.STRING)

@DiscriminatorValue("person")

public class Person {private int id;private String name;

 

  @Id

  @GeneratedValue

public int getId() {return id;}

繼承類中註解如下:

    只需要使用@DiscriminatorValue來註解標識欄位的值

4、 匯出後生成SQL語句:

create table t_animal (id integer not null auto_increment, typevarchar(255) not null, name varchar(255), sex bit, weight integer, heightinteger, primary key (id))

5、 資料庫中表結構:



6、 單表繼承資料儲存:

session = HibernateUtils.getSession();

            tx =session.beginTransaction();

   

            Pig pig = new Pig();

            pig.setName("豬豬");

            pig.setSex(true);

            pig.setWeight(100);

            session.save(pig);

           

            Bird bird = new Bird();

            bird.setName("鳥鳥");

            bird.setSex(false);

            bird.setHeight(50);

            session.save(bird);

           

            tx.commit();

儲存執行輸出SQL語句:

Hibernate: insert into t_animal (name, sex, weight, type) values (?, ?, ?,'P')//自動確定無height欄位,並且設定識別符號為P

Hibernate: insert into t_animal (name, sex, height, type) values (?, ?, ?,'B') //自動確定無weight欄位,並且設定識別符號為B

解釋:hibernate會根據單表繼承對映檔案的配置內容,自動在插入資料時哪個子類需要插入哪些欄位,並且自動插入識別符號字元值(在對映檔案中配置了)

7、 單表繼承對映資料載入(指定載入子類):

    session =HibernateUtils.getSession();

    tx =session.beginTransaction();

           

    Pig pig =(Pig)session.load(Pig.class, 1);

    System.out.println("pig.name=" + pig.getName());

    System.out.println("pig.weight=" + pig.getWeight());

           

    Bird bird =(Bird)session.load(Bird.class, 2);

    System.out.println("bird.name" + bird.getName());

    System.out.println("bird.height=" + bird.getHeight());

           

    tx.commit();

8、 載入執行輸出SQL語句:

Hibernate: select pig0_.id as id0_0_, pig0_.name as name0_0_, pig0_.sex assex0_0_, pig0_.weight as weight0_0_ from t_animal pig0_ where pig0_.id=? and pig0_.type='P'

//hibernate會根據對映檔案自動確定哪些欄位(雖然表中有height,但hibernate並不會載入)屬於這個子類,並且確定識別符號為B(已經在配置檔案中設定了)

pig.name=豬豬

pig.weight=100

Hibernate: select bird0_.id as id0_0_, bird0_.name as name0_0_, bird0_.sexas sex0_0_, bird0_.height as height0_0_ from t_animal bird0_ where bird0_.id=?and bird0_.type='B'

//hibernate會根據對映檔案自動確定哪些欄位雖然表中有weight,但hibernate並不會載入屬於這個子類,並且確定識別符號為B(已經在配置檔案中設定了)

bird.name=鳥鳥

bird.height=50

9、 單表繼承對映資料載入(指定載入父類):

session = HibernateUtils.getSession();

            tx =session.beginTransaction();

           

            //不會發出SQL,返回一個代理類

            Animal animal =(Animal)session.load(Animal.class, 1);

            //發出SQL語句,並且載入所有欄位的資料(因為使用父類載入物件資料)

            System.out.println("animal.name=" + animal.getName());

            System.out.println("animal.sex=" + animal.isSex());

           

            tx.commit();

載入執行生成SQL語句:

Hibernate: select animal0_.id as id0_0_, animal0_.name as name0_0_,animal0_.sex as sex0_0_, animal0_.weight asweight0_0_, animal0_.height as height0_0_, animal0_.type as type0_0_ from t_animal animal0_ where animal0_.id=?

//注:因為使用父類載入資料,所以hibernate會將所有欄位(height、weight、type)的資料全部載入,並且條件中沒有識別欄位type(也就不區分什麼子類,把所有子類全部載入上來。)

10、 單表繼承對映資料載入(指定載入父類,看能否鑑別真實物件):

            session = HibernateUtils.getSession();

            tx =session.beginTransaction();

           

            //不會發出SQL語句(load預設支援延遲載入(lazy)),返回一個animal的代理物件(此代理類是繼承Animal生成的,也就是說是Animal一個子類)

            Animal animal =(Animal)session.load(Animal.class, 1);

           

            //因為在上面返回的是一個代理類(父類的一個子類),所以animal不是Pig

            //通過instanceof是反應不出正直的物件型別的,因此load在預設情況下是不支援多型查詢的。

            if (animal instanceof Pig) {

                System.out.println("是豬");

            } else {

                System.out.println("不是豬");//這就是結果

            }

            System.out.println("animal.name=" + animal.getName());

            System.out.println("animal.sex=" + animal.isSex());

           

            tx.commit();

11、 多型查詢:

在hibernate載入資料的時候能鑑別出正直的型別(通過instanceof)

get支援多型查詢;load只有在lazy=false,才支援多型查詢;HQL支援多型查詢

12、 採用load,通過Animal查詢,將<class>標籤上的lazy設定為false

session = HibernateUtils.getSession();

            tx = session.beginTransaction();

           

            //會發出SQL語句,因為設定lazy為false,不支援延遲載入

            Animal animal =(Animal)session.load(Animal.class, 1);

            //可以正確的判斷出Pig的型別,因為lazy=false,返回具體的Pid型別

            //此時load支援多型查詢

            if (animal instanceof Pig) {

                System.out.println("是豬");//結果

            } else {

                System.out.println("不是豬");

            }

            System.out.println("animal.name=" + animal.getName());

            System.out.println("animal.sex=" + animal.isSex());

           

            tx.commit();

13、 採用get,通過Animal查詢,可以判斷出正直的型別

            session = HibernateUtils.getSession();

            tx = session.beginTransaction();

           

            //會發出SQL語句,因為get不支援延遲載入,返回的是正直的型別,

            Animal animal =(Animal)session.load(Animal.class, 1);

            //可以判斷出正直的型別

            //get是支援多型查詢

            if (animal instanceof Pig) {

                System.out.println("是豬");//結果

            } else {

                System.out.println("不是豬");

            }

            System.out.println("animal.name=" + animal.getName());

            System.out.println("animal.sex=" + animal.isSex());

           

            tx.commit();

14、 採用HQL查詢,HQL是否支援多型查詢

List animalList = session.createQuery("from Animal").list();

            for (Iteratoriter = animalList.iterator(); iter.hasNext();) {

                Animal a = (Animal)iter.next();

                //能夠正確鑑別出正直的型別,HQL是支援多型查詢的。

                if (a instanceof Pig) {

                    System.out.println("是Pig");

                } else if (a instanceof Bird) {

                    System.out.println("是Bird");

                }

            }

15、 通過HQL查詢表中所有的實體物件

* HQL語句:session.createQuery("fromjava.lang.Object").list();

    * 因為所有物件都是繼承Object類

List list = session.createQuery("from java.lang.Object").list();

            for (Iteratoriter = list.iterator(); iter.hasNext();){

                Object o =iter.next();

                if (o instanceof Pig) {

                    System.out.println("是Pig");

                } else {

                    System.out.println("是Bird");

                }

            }

(四) 具體表繼承JOINED

每個類對映成一個表(table per subclass)

物件模型不用變化,儲存模型需要變化


1、 關係模型:

每個類對映成一個表(table per subclass)



注:因為需要每個類都對映成一張表,所以Animal也對映成一張表(t_animal),表中欄位為實體類屬性

而pig子類也需要對映成一張表(t_pid),但為了與父類聯絡需要加入一個外來鍵(pidid)指向父類對映成的表(t_animal),欄位為子類的擴充套件屬性。Bird子類同樣也對映成一張表(t_bird),也加入一個外來鍵(birdid)指向父類對映成的表(t_animal),欄位為子類的擴充套件屬性。

2、 xml方式(每個類對映成一個表):

    <class name="com.wjt276.hibernate.Animal" table="t_animal">

        <id name="id"column="id"><!-- 對映主鍵 -->

            <generator class="native"/>

        </id>

        <property name="name"/><!-- 對映普通屬性 -->

        <property name="sex"/>

        <!--<joined-subclass>標籤:繼承對映 每個類對映成一個表 -->

        <joined-subclass name="com.wjt276.hibernate.Pig" table="t_pig">

<!-- <key>標籤:會在相應的表(當前對映的表)裡,加入一個外來鍵 , 參照指向當前類的父類(當前Class標籤物件的表(t_animal))-->

            <key column="pigid"/>

            <property name="weight"/>

        </joined-subclass>

        <joined-subclass name="com.wjt276.hibernate.Bird" table="t_bird">

            <key column="birdid"/>

            <property name="height"/>

        </joined-subclass>

    </class>

1、理解如何對映

        這種策略是使用joined-subclass標籤來定義子類的。父類、子類,每個類都對應一張資料庫表。

    在父類對應的資料庫表中,實際上會儲存所有的記錄,包括父類和子類的記錄;在子類對應的資料庫表中,

    這個表只定義了子類中所特有的屬性對映的欄位。子類與父類,通過相同的主鍵值來關聯。實現這種策略的時候,

    有如下步驟:

    父類用普通的<class>標籤定義即可

    父類不再需要定義discriminator欄位

    子類用<joined-subclass>標籤定義,在定義joined-subclass的時候,需要注意如下幾點:

    Joined-subclass標籤的name屬性是子類的全路徑名

    Joined-subclass標籤需要包含一個key標籤,這個標籤指定了子類和父類之間是通過哪個欄位來關聯的。

    如:<key column=”PARENT_KEY_ID”/>,這裡的column,實際上就是父類的主鍵對應的對映欄位名稱。

    Joined-subclass標籤,既可以被class標籤所包含(這種包含關係正是表明了類之間的繼承關係),

    也可以與class標籤平行。 當Joined-subclass標籤的定義與class標籤平行的時候,需要在Joined-subclass

    標籤中,新增extends屬性,裡面的值是父類的全路徑名稱。子類的其它屬性,像普通類一樣,定義在joined-subclass標籤的內部。

3、 annotation註解

因為,子類生成的表需要引用父類生成的表,所以只需要在父類設定具體表繼承對映就可以了,其它子類只需要使用@Entity註解就可以了

@Entity

@Inheritance(strategy=InheritanceType.JOINED)

public class Person {

    private int id;

    private String name;

    @Id

    @GeneratedValue

    public int getId() {return id;}

4、 匯出輸出SQL語句:

create table t_animal (id integer notnull auto_increment, name varchar(255), sex bit, primary key (id))

create table t_bird (birdid integernot null, height integer, primary key (birdid))

create table t_pig (pigid integer notnull, weight integer, primary key (pigid))

altertable t_bird add index FKCB5B05A4A554009D(birdid), add constraint FKCB5B05A4A554009D foreign key (birdid) referencest_animal (id)

alter table t_pig add index FK68F8743FE77AC32 (pigid), add constraint FK68F8743FE77AC32 foreign key (pigid) references t_animal (id)

//共生成三個表,並在子類表中各有一個外來鍵參照指向父類表

資料的儲存,不需要其它的任務變化,直接使用單表繼承儲存就可以了,載入也是一樣。

具體表繼承效率沒有單表繼承高,但是單表繼承會出現多餘的庸於欄位,具體表層次分明

(五) 類表繼承TABLE_PER_CLASS

每個具體類對映成一個表(table per concreteclass)(有一些限制)

物件模型不用變化,儲存模型需要變化


1、 關係模型:

每個具體類(Pig、Brid)對映成一個表(table per concrete class)(有一些限制)

t_pig

Id

Name

Sex

Weight

1

豬豬

True

100

t_bird

Id

Name

Sex

Height

2

鳥鳥

False

50

2、 xml方式:對映檔案:

    <class name="com.wjt276.hibernate.Animal" table="t_animal">

        <id name="id"column="id"><!-- 對映主鍵 -->

            <generator class="assigned"/><!-- 每個具體類對映一個表主鍵生成策略不可使用native -->       </id>  

        <property name="name"/><!-- 對映普通屬性 -->

        <property name="sex"/>

        <!--使用<union-subclass>標籤來對映"每個具體類對映成一張表"的對映關係

            ,實現上上面的表t_animal雖然對映到資料庫中,但它沒有任何作用。      -->

        <union-subclass name="com.wjt276.hibernate.Pig" table="t_pig">

            <property name="weight"/>

        </union-subclass>

        <union-subclass name="com.wjt276.hibernate.Bird" table="t_bird">

            <property name="height"/>

        </union-subclass>

    </class>

理解如何對映

    這種策略是使用union-subclass標籤來定義子類的。每個子類對應一張表,而且這個表的資訊是完備的,

    即包含了所有從父類繼承下來的屬性對映的欄位(這就是它跟joined-subclass的不同之處,

    joined-subclass定義的子類的表,只包含子類特有屬性對映的欄位)。實現這種策略的時候,有如下步驟:

    父類用普通<class>標籤定義即可

    子類用<union-subclass>標籤定義,在定義union-subclass的時候,需要注意如下幾點:

    Union-subclass標籤不再需要包含key標籤(與joined-subclass不同)

    Union-subclass標籤,既可以被class標籤所包含(這種包含關係正是表明了類之間的繼承關係),

    也可以與class標籤平行。 當Union-subclass標籤的定義與class標籤平行的時候,需要在Union-subclass

    標籤中,新增extends屬性,裡面的值是父類的全路徑名稱。子類的其它屬性,像普通類一樣,

    定義在Union-subclass標籤的內部。這個時候,雖然在union-subclass裡面定義的只有子類的屬性,

    但是因為它繼承了父類,所以,不需要定義其它的屬性,在對映到資料庫表的時候,依然包含了父類的所

    有屬性的對映欄位。

    注意:在儲存物件的時候id是不能重複的(不能使用自增生成主鍵)

3、 annotation註解

只需要對父類進行註解就可以了,

因為子類表的ID是不可以重複,所以一般的主鍵生成策略已經不適應了,只有表主鍵生成策略。

首先使用@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)來註解繼承對映,並且使用具體表繼承方式,使用@TableGenerator來申明一個表主鍵生成策略

再在主鍵上@GeneratedValue(generator="t_gen", strategy=GenerationType.TABLE)來註解生成策略為表生成策略,並且指定表生成策略的名稱

繼承類只需要使用@Entity進行註解就可以了

@Entity

@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)

@TableGenerator(

      name="t_gen",

      table="t_gen_table",

      pkColumnName="t_pk",

      valueColumnName="t_value",

      pkColumnValue="person_pk",

      initialValue=1,

      allocationSize=1

      )

public class Person {

private int id;

private String name;

 

  @Id

  @GeneratedValue(generator="t_gen", strategy=GenerationType.TABLE)

  public int getId() {return id;}

4、 匯出輸出SQL語句:

create table t_animal (id integer not null, name varchar(255), sex bit, primary key (id))

create table t_bird (id integer not null, name varchar(255), sex bit, height integer, primary key(id))

create table t_pig (id integer not null, name varchar(255), sex bit, weight integer, primary key(id))

注:表t_animal、 t_bird、t_pig並不是自增的,是因為bird、pig都是animal類,也就是說animal不可以有相同的ID號(Bird、Pig是型別,只是儲存的位置不同而以)

5、 資料庫表結構如下:



注:如果不想t_animal存在(因為它沒有實際的作用),可以設定<class>標籤中的abstract="true"(抽象表),這樣在匯出至資料庫時,就不會生成t_animal表了。

<class name="com.wjt276.hibernate.Animal" table="t_animal" abstract="true">

        <id name="id"column="id"><!-- 對映主鍵 -->

…………

(六) 三種繼承關聯對映的區別:

1、  第一種:它把所有的資料都存入一個表中,優點:效率好(操作的就是一個表);缺點:存在庸於欄位,如果將庸於欄位設定為非空,則就無法存入資料;

2、  第二種:層次分明,缺點:效率不好(表間存在關聯表)

3、  第三種:主鍵欄位不可以設定為自增主鍵生成策略。

一般使用第一種

第17課 hibernate樹形結構(重點)

         樹形結構:也就是目錄結構,有父目錄、子目錄、檔案等資訊,而在程式中樹形結構只是稱為節點。

         一棵樹有一個根節點,而根節點也有一個或多個子節點,而一個子節點有且僅有一個父節點(當前除根節點外),而且也存在一個或多個子節點。

         也就是說樹形結構,重點就是節點,也就是我們需要關心的節點物件。

         節點:一個節點有一個ID、一個名稱、它所屬的父節點(根節點無父節點或為null),有一個或多的子節點等其它資訊。

Hibernate將節點抽取出成實體類,節點相對於父節點是“多對一”對映關係,節點相對於子節點是“一對多”對映關係。

一、  節點實體類:

/** * 節點*/

public class Node {

    private int id; //識別符號

    private String name; //節點名稱 

    private int level; //層次,為了輸出設計   

    private boolean leaf; //是否為葉子節點,這是為了效率設計,可有可無

    //父節點:因為多個節點屬於一個父節點,因此用hibernate對映關係說是“多對一”

    private Node parent;

    //子節點:因為一個節點有多個子節點,因此用hibernate對映關係說是“一對多”

    private Set children;

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(Stringname) {  this.name = name;}

    public int getLevel() { return level;}

    public void setLevel(int level) {this.level = level;}

    public boolean isLeaf() {return leaf;}

    public void setLeaf(boolean leaf) {this.leaf = leaf;}

    public Node getParent() {return parent;}

    public void setParent(Nodeparent) {this.parent = parent;}

    public SetgetChildren() {return children;}

    public void setChildren(Setchildren) {this.children = children;}}

二、  xml方式:對映檔案:

  <class name="com.wjt276.hibernate.Node" table="t_node">

      <id name="id" column="id">

          <generator class="native"/>

      </id>

      <property name="name"/>

      <property name="level"/>

      <property name="leaf"/>

<!— 一對多:加入一個外來鍵,參照當前表t_node主鍵,    而屬性parent型別為Node,也就是當前類,則會在同一個表中加入這個欄位,參照這個表的主鍵-->

      <many-to-one name="parent" column="pid"/>

  <!--<set>標籤是對映一對多的方式,加入一個外來鍵,參照主鍵。-->

      <set name="children" lazy="extra"inverse="true">

          <key column="pid"/>

          <one-to-many class="com.wjt276.hibernate.Node"/>

      </set>     

  </class>

三、  annotation註解

因為樹型節點所有的資料,在資料庫中只是儲存在一個表中,而對於實體類來說,節點對子節點來說是一對多的關係,而對於父節點來說是多對一的關係。因此可以在一個實體類中註解。如下

@Entity

public class Node {

    private int id; // 識別符號

    private String name; // 節點名稱

    private int level; // 層次,為了輸出設計

    private boolean leaf; // 是否為葉子節點,這是為了效率設計,可有可無

    // 父節點:因為多個節點屬於一個父節點,因此用hibernate對映關係說是“多對一”

    private Node parent;

    // 子節點:因為一個節點有多個子節點,因此用hibernate對映關係說是“一對多”

    private Set<Node> children = newHashSet<Node>();

    @Id

    @GeneratedValue

    public int getId() {return id;}

    @OneToMany(mappedBy="parent")

    public Set<Node>getChildren() {return children;}

    @ManyToOne

    @JoinColumn(name="pid")

    public Node getParent() {return parent;}

四、  測試程式碼:

public class NodeTest extends TestCase {

  //測試節點的存在

  public void testSave1(){

      NodeManage.getInstanse().createNode("F:\\hibernate\\hibernate_training_tree");

  }

  //測試節點的載入

  public void testPrintById(){

      NodeManage.getInstanse().printNodeById(1);

  }}

五、  相應的類程式碼:

public classNodeManage {  

    private static NodeManage nodeManage= newNodeManage();

    private NodeManage(){}//因為要使用單例,所以將其構造方法私有化

    //向外提供一個介面

    public static NodeManage getInstanse(){

        return nodeManage;

    }

    /**

     * 建立樹

     *@param filePath 需要建立樹目錄的根目錄

     */

    public void createNode(String dir) {

        Session session = null;    

        try {

            session =HibernateUtils.getSession();

            session.beginTransaction();

           

            File root = new File(dir);

            //因為第一個節點無父節點,因為是null

            this.saveNode(root, session, null,0);

           

            session.getTransaction().commit();

        } catch (HibernateException e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        } finally {

            HibernateUtils.closeSession(session);

        }}

    /**

     * 儲存節點物件至資料庫

     *@param file 節點所對應的檔案

     *@param session session

     *@param parent 父節點

     *@param level 級別

     */

    public void saveNode(File file, Sessionsession, Node parent, int level) {

        if (file == null ||!file.exists()){return;}

        //如果是檔案則返回true,則表示是葉子節點,否則為目錄,非葉子節點

        boolean isLeaf = file.isFile();

        Node node = new Node();

        node.setName(file.getName());

        node.setLeaf(isLeaf);

        node.setLevel(level);

        node.setParent(parent);

       

        session.save(node);

       

        //進行迴圈迭代子目錄

        File[] subFiles = file.listFiles();

        if (subFiles != null &&subFiles.length > 0){

            for (int i = 0; i <subFiles.length ; i++){

                this.saveNode(subFiles[i],session, node, level + 1);

            }}}

    /**

     * 輸出樹結構

     *@param id

     */

    public void printNodeById(int id) {

        Session session = null;

       

        try {

            session =HibernateUtils.getSession();

            session.beginTransaction();

           

            Node node =(Node)session.get(Node.class, 1);

           

            printNode(node);

           

            session.getTransaction().commit();

        } catch (HibernateException e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        } finally {

            HibernateUtils.closeSession(session);

        }      

    }  

    private void printNode(Node node) {    

        if (node == null){  return; }

        int level = node.getLevel();

        if (level > 0){

            for (int i = 0; i < level; i++){

                System.out.print("  |");

            }

            System.out.print("--");            

        }

        System.out.println(node.getName() +(node.isLeaf() ? "" : "[" + node.getChildren().size() +"]"));

        Set children = node.getChildren();

        for (Iterator iter =children.iterator(); iter.hasNext(); ){

            Node child = (Node)iter.next();

            printNode(child);

        }}}


第18課 作業-學生、課程、分數的對映關係

一、        設計

1、  實體類(表)

2、  導航(程式設計方便)

a)        通過學生 取出 學生所先的課程

b)        但是通過課程 取出 學該課程的 學生不好。學的學生太多

c)         確定程式設計的方式

3、  可以利用聯合主鍵對映可以,

a)        學生生成一個表

b)        課程生成一個表

c)         再生成一個表,主鍵是聯合主鍵(學生ID、課程ID) + 學生共生成一個表

4、  也可以利用一對多,多對多 都可以(推薦)

a)        學生生成一個表

b)        課程生成一個表

c)         分數生成一個表,並且有兩個外來鍵,分別指向學生、課程表

二、        程式碼:

* 課程

@Entity

public class Course {

  private int id;

  private String name;

  @Id

  @GeneratedValue

  public int getId() {return id;}

  public void setId(int id) { this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {this.name = name;}}

* 分數

@Entity

@Table(name = "score")

public class Score {

  private int id;

  private int score;

  private Student student;

  private Course course;

  @Id

  @GeneratedValue

  public int getId() {return id;}

  @ManyToOne

  @JoinColumn(name = "student_id")

  public StudentgetStudent() {return student;}

  @ManyToOne

  @JoinColumn(name = "score_id")

  public Course getCourse(){ return course;}

  public int getScore() { return score;}

  public void setScore(int score) {this.score = score;}

  public void setStudent(Studentstudent) {this.student = student;}

  public void setCourse(Coursecourse) {this.course = course;}

  public void setId(int id) { this.id = id;}}

* 學生通過課程可以導航到分數

@Entity

public class Student {

  private int id;

  private String name;

  private Set<Course> courses = newHashSet<Course>();

  @Id

  @GeneratedValue

  public int getId() {return id;}

  @ManyToMany

  @JoinTable(name = "score", //此表就是Score實體類在資料庫生成的表叫score

          joinColumns= @JoinColumn(name = "student_id"),

          inverseJoinColumns= @JoinColumn(name = "course_id")

      )

  public Set<Course>getCourses() {return courses;}

  public voidsetCourses(Set<Course> courses) {this.courses = courses;}

  public void setId(int id) { this.id = id;}

  public String getName() {return name;}

  public void setName(Stringname) {this.name = name;}}

三、        注意

在Student實體類中的使用的第三方表使用了兩個欄位,而hibernate會使這兩個欄位生成聯合主鍵,這並不是我們需要的結果,因為我們需要手動到資料庫中修改。這樣才可以儲存資料,否則資料儲存不進去。這可能是hibernate的一個小bug

第19課 Hibernate查詢(Query Language)

HQL VS EJBQL

一、        Hibernate可以使用的查詢語言

1、  NativeSQL:本地語言(資料庫自己的SQL語句)

2、  HQL :Hibernate自帶的查詢語句,可以使用HQL語言,轉換成具體的方言

3、  EJBQL:JPQL 1.0,可以認為是HQL的一個子節(重點)

4、  QBC:Query By Cretira

5、  QBE:Query By Example

注意:上面的功能是從1至5的比較,1的功能最大,5的功能最小

二、        例項一

1、  版塊

/** 版塊*/

@Entity

public class Category {

    private int id;

    private String name;

    @Id

    @GeneratedValue

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getName() {return name;}

    public void setName(Stringname) {this.name = name;}}

2、  主題

/**主題*/

@Entity

public class Topic {

    private int id;

    private String title;

    private Category category;

    //private Category category2;

   

    private Date createDate;   

    public DategetCreateDate() {return createDate;}

    public void setCreateDate(DatecreateDate) {this.createDate = createDate;}

    @ManyToOne(fetch=FetchType.LAZY)

    public CategorygetCategory() { return category;}

    public voidsetCategory(Category category) {this.category = category;   }

    @Id

    @GeneratedValue

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getTitle(){return title;}

    public void setTitle(Stringtitle) {this.title = title;}}

3、  主題回覆

/**主題回覆*/

@Entity

public class Msg {

    private int id;

    private String cont;

    private Topic topic;   

    @ManyToOne

    public Topic getTopic() {return topic;}

    public void setTopic(Topictopic) {this.topic = topic;}

    @Id

    @GeneratedValue

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getCont() {return cont;}

    public void setCont(Stringcont) {this.cont = cont;}}

4、  臨時類

/**臨時類 */

public class MsgInfo { //VO DTO Value Object username p1 p2UserInfo->User->DB

    private int id;

    private String cont;

    private String topicName;

    private String categoryName;

    public MsgInfo(int id, String cont,String topicName, String categoryName) {

        super();

        this.id = id;

        this.cont = cont;

        this.topicName = topicName;

        this.categoryName =categoryName;

    }

    public StringgetTopicName() {return topicName;}

    public voidsetTopicName(String topicName) {this.topicName = topicName;}

    public StringgetCategoryName() {return categoryName;}

    public voidsetCategoryName(String categoryName) {

        this.categoryName =categoryName;

    }

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getCont() {return cont;}

    public void setCont(Stringcont) {this.cont = cont;}}

三、        實體一測試程式碼:

//初始化資料

    @Test

    public void testSave() {

        Sessionsession = sf.openSession();

        session.beginTransaction();    

        for(int i=0; i<10; i++){

            Categoryc = new Category();

            c.setName("c" + i);

            session.save(c);

        }      

        for(int i=0; i<10; i++){

            Categoryc = new Category();

            c.setId(1);

            Topic t= new Topic();

            t.setCategory(c);

            t.setTitle("t" + i);

            t.setCreateDate(new Date());

            session.save(t);           

        }      

        for(int i=0; i<10; i++){         

            Topic t= new Topic();

            t.setId(1);

            Msg m = new Msg();

            m.setCont("m" + i);

            m.setTopic(t);

            session.save(m);           

        }

        session.getTransaction().commit();

        session.close();

    }

        /** QL:from + 實體類名稱 */

        Query q =session.createQuery("from Category");

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getName());

        }

       

   

        /* 可以為實體類起個別名,然後使用它 */

        Query q =session.createQuery("from Category c wherec.name > 'c5'");

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getName());

        }

   

        //排序

        Query q =session.createQuery("from Category c orderby c.name desc");

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getName());

        }

   

         * 為載入上來的物件屬性起別名,還可以使用

        Query q =session.createQuery("select distinct c fromCategory c order by c.name desc");

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getName());

        }

   

        /*Query q = session.createQuery("from Category c where c.id > :minand c.id < :max");

        //q.setParameter("min",2);

        //q.setParameter("max",8);

        q.setInteger("min",2);

        q.setInteger("max",8);*/

         * 可以使用冒號(:),作為佔位符,來接受引數使用。如下(鏈式程式設計)

        Query q =session.createQuery("from Category c wherec.id > :min and c.id < :max")

            .setInteger("min", 2)

            .setInteger("max", 8);

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getId()+ "-" + c.getName());

        }

   

        Query q =session.createQuery("from Category c wherec.id > ? and c.id < ?");

        q.setParameter(0, 2)

            .setParameter(1, 8);

//      q.setParameter(1, 8);

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getId()+ "-" + c.getName());

        }

   

//分頁

        Query q =session.createQuery("from Category c orderby c.name desc");

        q.setMaxResults(4);//每頁顯示的最大記錄數

        q.setFirstResult(2);//從第幾條開始顯示,從0開始

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getId()+ "-" + c.getName());

        }

   

        Query q =session.createQuery("select c.id,  c.name from Category c order by c.namedesc");

        List<Object[]>categories = (List<Object[]>)q.list();

        for(Object[] o : categories) {

            System.out.println(o[0] + "-" + o[1]);

        }

   

    //設定fetch type 為lazy後將不會有第二條sql語句

        Query q =session.createQuery("from Topic t wheret.category.id = 1");

        List<Topic>topics = (List<Topic>)q.list();

        for(Topic t : topics) {

            System.out.println(t.getTitle());

            //System.out.println(t.getCategory().getName());

        }

   

    //設定fetch type 為lazy後將不會有第二條sql語句

        Query q =session.createQuery("from Topic t wheret.category.id = 1");

        List<Topic>topics = (List<Topic>)q.list();

        for(Topic t : topics) {

            System.out.println(t.getTitle());

        }

   

        Query q =session.createQuery("from Msg m wherem.topic.category.id = 1");

        for(Object o : q.list()) {

            Msg m = (Msg)o;

            System.out.println(m.getCont());

        }

   

//瞭解即可

    //VO Value Object

    //DTO data transfer object

        Query q =session.createQuery("select newcom.bjsxt.hibernate.MsgInfo(m.id, m.cont, m.topic.title, m.topic.category.name)from Msg");

        for(Object o : q.list()) {

            MsgInfo m = (MsgInfo)o;

            System.out.println(m.getCont());

        }

   

    //動手測試left right join

    //為什麼不能直接寫Category名,而必須寫t.category

    //因為有可能存在多個成員變數(同一個類),需要指明用哪一個成員變數的連線條件來做連線

        Query q =session.createQuery("select t.title, c.namefrom Topic t join t.category c "); //join Category c

        for(Object o : q.list()) {

            Object[] m = (Object[])o;

            System.out.println(m[0] + "-" + m[1]);

        }

   

    //學習使用uniqueResult

        Query q = session.createQuery("from Msg m where m = :MsgToSearch "); //不重要

        Msg m = new Msg();

        m.setId(1);

        q.setParameter("MsgToSearch", m);

        Msg mResult =(Msg)q.uniqueResult();

        System.out.println(mResult.getCont());

   

        Query q =session.createQuery("select count(*) fromMsg m");

        long count = (Long)q.uniqueResult();

        System.out.println(count);

   

    Query q = session.createQuery("select max(m.id), min(m.id), avg(m.id), sum(m.id)from Msg m");

        Object[] o =(Object[])q.uniqueResult();

        System.out.println(o[0] + "-" + o[1] + "-" + o[2] + "-" + o[3]);

   

        Query q =session.createQuery("from Msg m where m.idbetween 3 and 5");

        for(Object o : q.list()) {

            Msg m = (Msg)o;

            System.out.println(m.getId()+ "-" + m.getCont());

        }

   

        Query q =session.createQuery("from Msg m where m.idin (3,4, 5)");

        for(Object o : q.list()) {

            Msg m = (Msg)o;

            System.out.println(m.getId()+ "-" + m.getCont());

        }

   

    //is null 與 is notnull

        Query q =session.createQuery("from Msg m where m.contis not null");

        for(Object o : q.list()) {

            Msg m = (Msg)o;

            System.out.println(m.getId()+ "-" + m.getCont());

        }

   

四、        例項二

注意:實體二,實體類,只是在實體一的基礎上修改了Topic類,新增了多對一的關聯關係

@Entity

@NamedQueries({

            @NamedQuery(name="topic.selectCertainTopic", query="from Topic t where t.id = :id")

        })

        /*@NamedNativeQueries(

        {

    @NamedNativeQuery(name="topic.select2_5Topic",query="select * from topic limit 2, 5")

        })*/

public class Topic {

    private int id;

    private String title;

    private Category category;

    private Date createDate;

    private List<Msg> msgs = newArrayList<Msg>();

    @OneToMany(mappedBy="topic")

    public List<Msg> getMsgs() {return msgs;}

    public void setMsgs(List<Msg> msgs) {this.msgs = msgs;}

    public Date getCreateDate() {return createDate;}

    public void setCreateDate(Date createDate) {this.createDate = createDate;   }

    @ManyToOne(fetch=FetchType.LAZY)

    public Category getCategory() { return category;}

    public void setCategory(Category category) {this.category = category;}

    @Id

    @GeneratedValue

    public int getId() {return id;}

    public void setId(int id) {this.id = id;}

    public String getTitle() {  return title;}

    public void setTitle(String title) {this.title = title;}}

五、        例項二測試程式碼

注意:測試資料是例項一的測試資料 

    //is empty and is not empty

        Query q =session.createQuery("from Topic t wheret.msgs is empty");

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getId()+ "-" + t.getTitle());

        }

   

        Query q =session.createQuery("from Topic t wheret.title like '%5'");

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getId()+ "-" + t.getTitle());

        }

   

        Query q =session.createQuery("from Topic t wheret.title like '_5'");

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getId()+ "-" + t.getTitle());

        }

    //不重要

        Query q = session.createQuery("select lower(t.title)," +

                                             "upper(t.title)," +

                                             "trim(t.title)," +

                                             "concat(t.title,'***')," +

                                             "length(t.title)" +

                                             " from Topict ");

        for(Object o : q.list()) {

            Object[] arr = (Object[])o;

    System.out.println(arr[0] + "-" + arr[1] + "-" + arr[2] + "-" + arr[3] + "-" + arr[4] + "-");

        }

   

        Query q =session.createQuery("select abs(t.id)," +

                                             "sqrt(t.id)," +

                                             "mod(t.id,2)" +

                                             " from Topict ");

        for(Object o : q.list()) {

            Object[] arr =(Object[])o;

            System.out.println(arr[0] + "-" + arr[1] + "-" + arr[2] );

        }

   

Query q = session.createQuery("selectcurrent_date, current_time, current_timestamp, t.id from Topic t");

        for(Object o : q.list()) {

            Object[] arr =(Object[])o;

            System.out.println(arr[0] + " | " + arr[1] + " | " + arr[2] + " | " + arr[3]);

        }

   

        Query q =session.createQuery("from Topic t wheret.createDate < :date");

        q.setParameter("date", new Date());

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getTitle());

        }

   

        Query q =session.createQuery("select t.title,count(*) from Topic t group by t.title") ;

        for(Object o : q.list()) {

            Object[] arr =(Object[])o;

            System.out.println(arr[0] + "|" + arr[1]);

        }

   

Query q = session.createQuery("selectt.title, count(*) from Topic t group by t.title having count(*) >= 1") ;

        for(Object o : q.list()) {

            Object[] arr =(Object[])o;

            System.out.println(arr[0] + "|" + arr[1]);

        }

   

        Query q =session.createQuery("from Topic t where t.id< (select avg(t.id) from Topic t)") ;

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getTitle());

        }

   

        Query q =session.createQuery("from Topic t where t.id< ALL (select t.id from Topic t where mod(t.id, 2)= 0) ") ;

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getTitle());

        }

   

    //用in 可以實現exists的功能

    //但是exists執行效率高

// t.id not in (1)

        Query q =session.createQuery("from Topic t where notexists (select m.id from Msg m where m.topic.id=t.id)") ;

//      Query q =session.createQuery("from Topic t where exists (select m.id from Msg mwhere m.topic.id=t.id)") ;

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getTitle());

        }

   

    //update and delete

    //規範並沒有說明是不是要更新persistent object,所以如果要使用,建議在單獨的trasaction中執行

        Query q =session.createQuery("update Topic t sett.title = upper(t.title)") ;

        q.executeUpdate();

        q = session.createQuery("from Topic");

        for(Object o : q.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getTitle());

        }

        session.createQuery("update Topic t set t.title = lower(t.title)")

            .executeUpdate();

   

//不重要

        Query q =session.getNamedQuery("topic.selectCertainTopic");

        q.setParameter("id", 5);

        Topic t =(Topic)q.uniqueResult();

        System.out.println(t.getTitle());

   

    //Native(瞭解)

        SQLQuery q =session.createSQLQuery("select *from category limit 2,4").addEntity(Category.class);

        List<Category>categories = (List<Category>)q.list();

        for(Category c : categories) {

            System.out.println(c.getName());

        }

   

    public void testHQL_35() {

        //尚未實現JPA命名的NativeSQL

    }

第20課 Query by Criteria(QBC)

QBC(Query By Criteria)查詢方式是Hibernate提供的“更加物件導向”的一種檢索方式。QBC在條件查詢上比HQL查詢更為靈活,而且支援執行時動態生成查詢語句。

在Hibernate應用中使用QBC查詢通常經過3個步驟 
  (1)使用Session例項的createCriteria()方法建立Criteria物件 
  (2)使用工具類Restrictions的相關方法為Criteria物件設定查詢物件 
  (3)使用Criteria物件的list()方法執行查詢,返回查詢結果

一、        實體程式碼:

注意:資料是使用Hibernate查詢章節的資料

        //criterion 標準/準則/約束

        Criteria c =session.createCriteria(Topic.class) //from Topic                 

                     .add(Restrictions.gt("id", 2)) //greater than = id > 2

                     .add(Restrictions.lt("id", 8)) //little than = id < 8

                     .add(Restrictions.like("title", "t_"))

                     .createCriteria("category")

                .add(Restrictions.between("id", 3, 5)) //category.id >= 3 and category.id <=5

                     ;

        //DetachedCriterea

        for(Object o : c.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getId()+ "-" + t.getTitle());

        }

二、        Restrictions用法

Hibernate中Restrictions的方法            說明  
Restrictions.eq                         =  
Restrictions.allEq                       利用Map來進行多個等於的限制  
Restrictions.gt                          >  
Restrictions.ge                          >=  
Restrictions.lt                          < 
Restrictions.le                          <=  
Restrictions.between                     BETWEEN 
Restrictions.like                        LIKE 
Restrictions.in                          in 
Restrictions.and                              and 
Restrictions.or                          or 
Restrictions.sqlRestriction              用SQL限定查詢

============================================

QBE (QueryBy Example)
  Criteria cri = session.createCriteria(Student.class);
  cri.add(Example.create(s)); //s是一個Student物件
  list cri.list();   
  實質:建立一個模版,比如我有一個表serial有一個 giftortoy欄位,我設定serial.setgifttoy("2"),
        則這個表中的所有的giftortoy為2的資料都會出來

2: QBC (Query By Criteria) 主要有Criteria,Criterion,Oder,Restrictions類組成
  session = this.getSession();
  Criteria cri = session.createCriteria(JdItemSerialnumber.class);
  Criterion cron = Restrictions.like("customer",name);
  cri.add(cron);
  list = cri.list();
  ==============================

Hibernate中 Restrictions.or()和Restrictions.disjunction()的區別是什麼?
  比較運算子
  HQL運算子                  QBC運算子                     含義
     =                    Restrictions.eq()                 等於
     <>                  Restrictions.not(Exprission.eq())  不等於
     >                    Restrictions.gt()                 大於
     >=                  Restrictions.ge()                 大於等於
     <                    Restrictions.lt()                 小於
     <=                  Restrictions.le()                 小於等於
     is null            Restrictions.isnull()              等於空值
     is not null      Restrictions.isNotNull()          非空值
     like                Restrictions.like()               字串模式匹配
     and               Restrictions.and()                邏輯與
     and               Restrictions.conjunction()         邏輯與
     or                  Restrictions.or()                 邏輯或
     or                  Restrictions.disjunction()         邏輯或
     not                 Restrictions.not()                邏輯非
     in(列表)         Restrictions.in()                 等於列表中的某一個值
     ont in(列表)        Restrictions.not(Restrictions.in())不等於列表中任意一個值
     between x and y      Restrictions.between()            閉區間xy中的任意值
     not between x and y Restrictions.not(Restrictions..between()) 小於值X或者大於值y

3: HQL
  String hql = "select s.name ,avg(s.age) from Student s group bys.name";
  Query query = session.createQuery(hql);
  list = query.list();
  ....

4: 本地SQL查詢
  session = sessionFactory.openSession();
  tran = session.beginTransaction();
  SQLQuery sq = session.createSQLQuery(sql);
  sq.addEntity(Student.class);
  list = sq.list();
  tran.commit();

5: QID 
  Session的get()和load()方法提供了根據物件ID來檢索物件的方式。該方式被用於事先知道了要檢索物件ID的情況。

三、 工具類Order提供設定排序方式

Order.asc(String propertyName) 
升序排序 
Order.desc(String propertyName) 
降序排序 

四、 工具類Projections提供對查詢結果進行統計與分組操作

Porjections.avg(String propertyName) 
求某屬性的平均值 
Projections.count(String propertyName) 
統計某屬性的數量 
Projections.countDistinct(String propertyName) 
統計某屬性的不同值的數量 
Projections.groupProperty(String propertyName) 
指定一組屬性值 
Projections.max(String propertyName) 
某屬性的最大值 
Projections.min(String propertyName) 
某屬性的最小值 
Projections.projectionList() 
建立一個新的projectionList物件 
Projections.rowCount() 
查詢結果集中記錄的條數 
Projections.sum(String propertyName) 
返回某屬性值的合計

五、 QBC分頁查詢

  Criteria為我們提供了兩個有用的方法:setFirstResult(intfirstResult)和setMaxResults(int maxResults).
setFirstResult(int firstResult)方法用於指定從哪一個物件開始檢索(序號從0開始),預設為第一個物件(序號為0);setMaxResults(int maxResults)方法用於指定一次最多檢索出的物件數目,預設為所有物件。

Session session = HibernateSessionFactory.getSessionFactory().openSession(); 

Transaction ts = null; 

Criteria criteria = session.createCriteria(Order.class); 

int pageSize = 15;  

int pageNo = 1;  

criteria.setFirstResult((pageNo-1)*pageSize);  

criteria.setMaxResults(pageSize); 

Iterator it = criteria.list().iterator(); 

ts.commit(); 

HibernateSessionFactory.closeSession(); 

六、 QBC複合查詢

   複合查詢就是在原有的查詢基礎上再進行查詢。例如在顧客對定單的一對多關係中,在查詢出所有的顧客物件後,希望在查詢定單中money大於1000的定單物件。

Session session = HibernateSessionFactory.getSessionFactory().openSession(); 

Transaction ts = session.beginTransaction(); 

Criteria cuscriteria = session.createCriteria(Customer.class); 

Criteria ordCriteria = cusCriteria.createCriteria("orders"); 

ordCriteria.add(Restrictions.gt("money", new Double(1000)));  

Iterator it = cusCriteria.list().iterator(); 

ts.commit(); 

HibernateSessionFactory.closeSession(); 

七、 QBC離線查詢

   離線查詢又叫DetachedCriteria查詢,它可以在Session之外進行構造,只有在需要執行查詢時才與Session繫結。Session session =HibernateSessionFactory.getSessionFactory().openSession();

Transaction ts =session.beginTransaction();

Criteria cuscriteria =session.createCriteria(Customer.class);

Criteria ordCriteria =cusCriteria.createCriteria("orders");

ordCriteria.add(Restrictions.gt("money",new Double(1000)));

Iterator it =cusCriteria.list().iterator();

ts.commit();

HibernateSessionFactory.closeSession();

第21課 Query By Example(QBE)

QBE查詢就是檢索與指定樣本物件具有相同屬性值的物件。因此QBE查詢的關鍵就是樣本物件的建立,樣本物件中的所有非空屬性均將作為查詢條件。QBE查詢的功能子集,雖然QBE沒有QBC功能大,但是有些場合QBE使用起來更為方便。
        工具類Example為Criteria物件指定樣本物件作為查詢條件

一、        例項程式碼

Session session = sf.openSession();

        session.beginTransaction();

        Topic tExample = new Topic();

        tExample.setTitle("T_");       

        Example e = Example.create(tExample)

                    .ignoreCase().enableLike();

        Criteria c = session.createCriteria(Topic.class)

                     .add(Restrictions.gt("id", 2))

                     .add(Restrictions.lt("id", 8))

                     .add(e);      

        for(Object o : c.list()) {

            Topic t = (Topic)o;

            System.out.println(t.getId() + "-" + t.getTitle());

        }

        session.getTransaction().commit();

        session.close();

Session session = HibernateSessionFactory.getSessionFactory().openSession(); 

Transaction ts = session.beginTransaction(); 

Customer c = new Customer(); 

c.setCname("Hibernate");  

Criteria criteria = session.createCriteria(Customer.class); 

Criteria.add(Example.create(c));  

Iterator it = criteria.list().iterator(); 

ts.commit();  

HibernateSessionFactory.closeSession();

第22課 Query.list與query.iterate(不太重要)

一、         query.iterate查詢資料

* query.iterate()方式返回迭代查詢

     * 會開始發出一條語句:查詢所有記錄ID語句

     * Hibernate: select student0_.id as col_0_0_from t_student student0_

     * 然後有多少條記錄,會發出多少條查詢語句。

     * n + 1問題:n:有n條記錄,發出n條查詢語句;1 :發出一條查詢所有記錄ID語句。

     * 出現n+1的原因:因為iterate(迭代查詢)是使用快取的,

                    第一次查詢資料時發出查詢語句載入資料並加入到快取,以後再查詢時hibernate會先到ession快取(一級快取)中檢視資料是否存在,如果存在則直接取出使用,否則發出查詢語句進行查詢。

session= HibernateUtils.getSession();

            tx = session.beginTransaction();

           

            /**

             * 出現N+1問題

             * 發出查詢id列表的sql語句

             * Hibernate: select student0_.id as col_0_0_ from t_student student0_

             *

             * 再依次發出根據id查詢Student物件的sql語句

             * Hibernate: select student0_.id as id1_0_, student0_.name as name1_0_,

             * student0_.createTime as createTime1_0_, student0_.classesid as classesid1_0_

             * from t_student student0_ where student0_.id=?

             */

            Iterator students = session.createQuery("fromStudent").iterate();

           

            while (students.hasNext()){

                Student student =(Student)students.next();

                System.out.println(student.getName());

            }

tx.commit();

二、         query.list()和query.iterate()的區別

先執行query.list(),再執行query.iterate,這樣不會出現N+1問題,

     * 因為list操作已經將Student物件放到了一級快取中,所以再次使用iterate操作的時候

     * 它首先發出一條查詢id列表的sql,再根據id到快取中取資料,只有在快取中找不到相應的

     * 資料時,才會發出sql到資料庫中查詢

Liststudents = session.createQuery("from Student").list();

           

            for (Iterator iter = students.iterator();iter.hasNext();){

                Student student =(Student)iter.next();

                System.out.println(student.getName());

            }                       System.out.println("---------------------------------------------------------");

             // 不會出現N+1問題,因為list操作已經將資料加入到一級快取。

            Iterator iters =session.createQuery("from Student").iterate();

           

            while (iters.hasNext()){

                Student student =(Student)iters.next();

                System.out.println(student.getName());

            }

三、         兩次query.list()

     * 會再次發出查詢sql

     * 在預設情況下list每次都會向資料庫發出查詢物件的sql,除非配置了查詢快取

     * 所以:雖然list操作已經將資料放到一級快取,但list預設情況下不會利用快取,而再次發出sql

     * 預設情況下,list會向快取中放入資料,但不會使用資料。

Liststudents = session.createQuery("from Student").list();

           

            for (Iterator iter = students.iterator();iter.hasNext();){

                Student student =(Student)iter.next();

                System.out.println(student.getName());

            }

           

            System.out.println("------------------------------------------------");

           

            //會再次發現SQL語句進行查詢,因為預設情況list只向快取中放入資料,不會使用快取中資料

            students = session.createQuery("fromStudent").list();

           

            for (Iterator iter = students.iterator();iter.hasNext();){

                Student student =(Student)iter.next();

                System.out.println(student.getName());

            }

第23課 效能優化策略

1、  注意session.clear()的動用,尤其在不斷分頁迴圈的時候

a)        在一個大集合中進行遍歷,遍歷msg,取出其中的含有敏感字樣的物件

b)        另外一種形式的記憶體洩露 //面試是:Java有記憶體洩漏嗎?

2、  1 + N問題 //典型的面試題

a)        Lazy

b)        BatchSize   設定在實體類的前面

c)         joinfetch

3、  list 和 iterate不同之處

a)        list取所有

b)        Iterate先取ID,等用到的時候再根據ID來取物件

c)         session中list第二次發出,仍會到資料庫查詢

d)        iterate第二次,首先找session級快取

第24課 hibernate快取

一、 Session級快取(一級快取)

一級快取很短和session的生命週期一致,因此也叫session級快取或事務級快取

         hibernate一級快取

        

那些方法支援一級快取:

         * get()

         * load()

         * iterate(查詢實體物件)

        

如何管理一級快取:

         *session.clear(),session.evict()

        

如何避免一次性大量的實體資料入庫導致記憶體溢位

         * 先flush,再clear

        

如果資料量特別大,考慮採用jdbc實現,如果jdbc也不能滿足要求可以考慮採用資料本身的特定匯入工具

二、 二級快取

Hibernate預設的二級快取是開啟的。

         二級快取也稱為程式級的快取,也可稱為SessionFactory級的快取(因為SessionFactory可以管理二級快取),它與session級快取不一樣,一級快取只要session關閉快取就不存在了。而二級快取則只要程式在二級快取就可用。

         二級快取可以被所有的session共享

         二級快取的生命週期和SessionFactory的生命週期一樣,SessionFactory可以管理二級快取

         二級快取同session級快取一樣,只快取實體物件,普通屬性的查詢不會快取

         二級快取一般使用第三方的產品,如EHCache

1、   二級快取的配置和使用:

         配置二級快取的配置檔案:模板檔案位於hibernate\etc目錄下(如ehcache.xml),將模板存放在ClassPath目錄中,一般放在根目錄下(src目錄下)

<ehcache>

    <!-- 設定當快取物件益出時,物件儲存到磁碟時的儲存路徑。

            如 d:\xxxx

         The following properties aretranslated:

         user.home - User's home directory

         user.dir - User's current workingdirectory

         java.io.tmpdir - windows的臨時目錄 -->

    <diskStore path="java.io.tmpdir"/>

    <!--預設配置/或對某一個類進行管理

        maxInMemory       - 快取中可以存入的最多個物件數

        eternal           - true:表示永不失效,false:不是永久有效的。

        timeToIdleSeconds - 空閒時間,當第一次訪問後在空閒時間內沒有訪問,則物件失效,單位為秒

        timeToLiveSeconds - 被快取的物件有效的生命時間,單位為秒

      overflowToDisk  當快取中物件數超過核定數(益出時)時,物件是否儲存到磁碟上。true:儲存;false:不儲存

                         如果儲存,則儲存路徑在標籤<diskStore>中屬性path指定

        -->

    <defaultCache

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="120"

        timeToLiveSeconds="120"

        overflowToDisk="true"

        />      

</ehcache>

2、   二級快取的開啟:

         Hibernate中二級快取預設就是開啟的,也可以顯示的開啟

         二級快取是hibernate的配置檔案設定如下:

<!--開啟二級快取,hibernate預設的二級快取就是開啟的 -->

        <property name="hibernate.cache.use_second_level_cache">true</property>

3、   指定二級快取產品提供商:

         修改hibernate的 配置檔案,指定二級快取提供商,如下:

<!--指定二級快取提供商-->

<property name="hibernate.cache.provider_class">

org.hibernate.cache.EhCacheProvider

</property>

以下為常見快取提供商:

Cache

Provider class

Type

Cluster Safe

Query Cache Supported

Hashtable (not intended for production use)

org.hibernate.cache.HashtableCacheProvider

memory

 

yes

EHCache

org.hibernate.cache.EhCacheProvider

memory, disk

 

yes

OSCache

org.hibernate.cache.OSCacheProvider

memory, disk

 

yes

SwarmCache

org.hibernate.cache.SwarmCacheProvider

clustered (ip multicast)

yes (clustered invalidation)

 

JBoss TreeCache

org.hibernate.cache.TreeCacheProvider

clustered (ip multicast), transactional

yes (replication)

yes (clock sync req.)

4、   使用二級快取

a)       xml方式:指定哪些實體類使用二級快取:

         方法一:在實體類對映檔案中,使用<cache>來指定那個實體類使用二級快取,如下:

<cache 
    usage="transactional|read-write|nonstrict-read-write|read-only"  (1)
    region="RegionName"                                              (2)
    include="all|non-lazy"                                           (3)
/>

(1)   usage(必須)說明了快取的策略: transactional、 read-writenonstrict-read-writeread-only

(2)   region (可選, 預設為類或者集合的名字(class orcollection role name)) 指定第二級快取的區域名(name of the secondlevel cache region)

(3)   include (可選,預設為 allnon-lazy 當屬性級延遲抓取開啟時, 標記為lazy="true"的實體的屬性可能無法被快取

另外(首選?), 你可以在hibernate.cfg.xml中指定<class-cache>和 <collection-cache> 元素。

這裡的usage 屬性指明瞭快取併發策略(cache concurrency strategy)

策略:只讀快取(Strategy:read only)

如果你的應用程式只需讀取一個持久化類的例項,而無需對其修改, 那麼就可以對其進行只讀 快取。這是最簡單,也是實用性最好的方法。甚至在叢集中,它也能完美地運作。

<class name="eg.Immutable" mutable="false">
    <cache usage="read-only"/>
    ....
</class>

策略:讀/寫快取(Strategy:read/write)

如果應用程式需要更新資料,那麼使用/寫快取 比較合適。 如果應用程式要求“序列化事務”的隔離級別(serializable transaction isolation level),那麼就決不能使用這種快取策略。 如果在JTA環境中使用快取,你必須指定hibernate.transaction.manager_lookup_class屬性的值, 通過它,Hibernate才能知道該應用程式中J他的TransactionManager的具體策略。 在其它環境中,你必須保證在Session.close()、或Session.disconnect()呼叫前, 整個事務已經結束。 如果你想在叢集環境中使用此策略,你必須保證底層的快取實現支援鎖定(locking)。Hibernate內建的快取策略並不支援鎖定功能。

<class name="eg.Cat" .... >
    <cache usage="read-write"/>
    ....
    <set name="kittens" ... >
        <cache usage="read-write"/>
        ....
    </set>
</class>

策略:非嚴格讀/寫快取(Strategy: nonstrictread/write)

如果應用程式只偶爾需要更新資料(也就是說,兩個事務同時更新同一記錄的情況很不常見),也不需要十分嚴格的事務隔離,那麼比較適合使用非嚴格讀/寫快取策略。如果在JTA環境中使用該策略,你必須為其指定hibernate.transaction.manager_lookup_class屬性的值, 在其它環境中,你必須保證在Session.close()、或Session.disconnect()呼叫前, 整個事務已經結束。

策略:事務快取(transactional)

Hibernate的事務快取策略提供了全事務的快取支援, 例如對JBoss TreeCache的支援。這樣的快取只能用於JTA環境中,你必須指定為其hibernate.transaction.manager_lookup_class屬性。

沒有一種快取提供商能夠支援上列的所有快取併發策略。下表中列出了各種提供器、及其各自適用的併發策略。

表 19.2.  各種快取提供商對快取併發策略的支援情況(Cache Concurrency Strategy Support)

Cache

read-only

nonstrict-read-write

read-write

transactional

Hashtable (not intended for production use)

yes

yes

yes

 

EHCache

yes

yes

yes

 

OSCache

yes

yes

yes

 

SwarmCache

yes

yes

  

JBoss TreeCache

yes

  

yes

注:此方法要求:必須要標籤<cache>放在<id>標籤之前

<class name="com.wjt276.hibernate.Student"table="t_student">

        <!--  指定實體類使用二級快取 -->

        <cache usage="read-only"/>//***********

        <id name="id"column="id">

            <generator class="native"/>

        </id>

        <property name="name"column="name"/>

        <!--

            使用多對一標籤對映 一對多雙向,下列的column值必需與多的一端的key欄位值一樣。

         -->

        <many-to-one name="classes"column="classesid"/>

    </class>

         方法二:在hibernate配置檔案(hibernate.cfg.xml)使用<class-cache>標籤中指定

                   要求:<class-cache>標籤必須放在<maping>標籤之後。

<hibernate-configuration>

    <session-factory>  

        …………

<mapping resource="com/wjt276/hibernate/Classes.hbm.xml"/>

        <mapping resource="com/wjt276/hibernate/Student.hbm.xml"/>

       

        <class-cache class="com.wjt276.hibernate.Student"usage="read-only"/>

    </session-factory>

</hibernate-configuration>

    一般推薦使用方法一。

b)       annotation註解

為了優化資料庫訪問,你可以啟用所謂的Hibernate二級快取.該快取是可以按每個實體和集合進行配置的.

@org.hibernate.annotations.Cache定義了快取策略及給定的二級快取的範圍. 此註解適用於根實體(非子實體),還有集合.

@Entity

@Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

publicclass Forest { ... }

    @OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)

    @JoinColumn(name="CUST_ID")

    @Cache(usage =CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

    public SortedSet<Ticket> getTickets(){

        return tickets;

    }

@Cache(

    CacheConcurrencyStrategy usage();                 (1)

    String region() default "";                       (2)

    String include() default"all";                   (3)

)

(1)

usage: 給定快取的併發策略(NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL)

(2)

region (可選的):快取範圍(預設為類的全限定類名或是集合的全限定角色名)

(3)

include (可選的):值為all時包括了所有的屬性(proterty), 為non-lazy時僅含非延遲屬性(預設值為all)

5、   應用範圍

     沒有變化,近似於靜態的資料。

6、   二級快取的管理:

1、   清除指定實體類的所有資料

SessionFactory.evict(Student.class);

2、   清除指定實體類的指定物件

SessionFactory.evict(Student.class, 1);//第二個引數是指定物件的ID,就可以清除指定ID的物件

使用SessionFactory清除二級快取

Sessionsession = null;

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            Student student = (Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

       

        //管理二級快取

        SessionFactory factory = HibernateUtils.getSessionFactory();

        //factory.evict(Student.class);

        factory.evict(Student.class, 1);

       

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //會發出查詢sql,因為二級快取中的資料被清除了

            Student student = (Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

    }

7、   二級快取的互動

Sessionsession = null;

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //僅向二級快取讀資料,而不向二級快取寫資料

            session.setCacheMode(CacheMode.GET);

            Student student =(Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

       

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //發出sql語句,因為session設定了CacheMode為GET,所以二級快取中沒有資料

            Student student =(Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

       

        try {

            session = HibernateUtils.getSession();

            session.beginTransaction();

           

            //只向二級快取寫資料,而不從二級快取讀資料

            session.setCacheMode(CacheMode.PUT);

           

            //會發出查詢sql,因為session將CacheMode設定成了PUT

            Student student =(Student)session.load(Student.class, 1);

            System.out.println("student.name=" +student.getName());

           

            session.getTransaction().commit();

        }catch(Exception e) {

            e.printStackTrace();

            session.getTransaction().rollback();

        }finally {

            HibernateUtils.closeSession(session);

        }

CacheMode引數用於控制具體的Session如何與二級快取進行互動。

·                                CacheMode.NORMAL - 從二級快取中讀、寫資料。

·                                CacheMode.GET - 從二級快取中讀取資料,僅在資料更新時對二級快取寫資料。

·                                CacheMode.PUT - 僅向二級快取寫資料,但不從二級快取中讀資料。

·                                CacheMode.REFRESH - 僅向二級快取寫資料,但不從二級快取中讀資料。通過hibernate.cache.use_minimal_puts的設定,強制二級快取從資料庫中讀取資料,重新整理快取內容。

如若需要檢視二級快取或查詢快取區域的內容,你可以使用統計(Statistics API。

Map cacheEntries = sessionFactory.getStatistics()
        .getSecondLevelCacheStatistics(regionName)
        .getEntries();

此時,你必須手工開啟統計選項。可選的,你可以讓Hibernate更人工可讀的方式維護快取內容。

hibernate.generate_statistics true
hibernate.cache.use_structured_entries true

8、   總結

load預設使用二級快取,iterate預設使用二級快取

list預設向二級快取中加資料,但是查詢時候不使用

三、 查詢快取

查詢快取,是用於快取普通屬性查詢的,當查詢實體時快取實體ID。
預設情況下關閉,需要開啟。查詢快取,對list/iterator這樣的操作會起作用。
可以使用<property name=”hibernate.cache.use_query_cache”>true</property>來開啟查詢快取,預設為關閉。
所謂查詢快取:即讓hibernate快取list、iterator、createQuery等方法的查詢結果集。如果沒有開啟查詢快取,hibernate將只快取load方法獲得的單個持久化物件。
在開啟了查詢快取之後,需要注意,呼叫query.list()操作之前,必須顯式呼叫query.setCachable(true)來標識某個查詢使用快取。
查詢快取的生命週期:當前關聯的表發生修改,那麼查詢快取生命週期結束
注意查詢快取依賴於二級快取,因為使用查詢快取需要開啟二級快取

查詢快取的配置和使用:

    * 在hibernate.cfg.xml檔案中啟用查詢快取,如:

    <propertyname="hibernate.cache.use_query_cache">true</property>

    * 在程式中必須手動啟用查詢快取,如:

         query.setCacheable(true);
 
例如:

session= HibernateUtils.getSession();

            session.beginTransaction();

            Query query = session.createQuery("selects.name from Student s");

            //啟用查詢查詢快取

            query.setCacheable(true);

           

            List names = query.list();

            for (Iterator iter=names.iterator();iter.hasNext();) {

                String name =(String)iter.next();

                System.out.println(name);

            }

            System.out.println("-------------------------------------");

            query = session.createQuery("selects.name from Student s");

            //啟用查詢查詢快取

            query.setCacheable(true);

           

            //沒有發出查詢sql,因為啟用了查詢快取

            names = query.list();

            for (Iteratoriter=names.iterator();iter.hasNext(); ) {

                String name =(String)iter.next();

                System.out.println(name);

            }

         session.getTransaction().commit();
 

Session session = sf.openSession();

        session.beginTransaction();

        List<Category>categories = (List<Category>)session.createQuery("from Category")

                                    .setCacheable(true).list();

        session.getTransaction().commit();

        session.close();

       

        Session session2 = sf.openSession();

        session2.beginTransaction();

        List<Category>categories2 = (List<Category>)session2.createQuery("from Category")

        .setCacheable(true).list();

       

        session2.getTransaction().commit();

         session2.close();
 
注:查詢快取的生命週期與session無關。
查詢快取只對query.list()起作用,query.iterate不起作用,也就是query.iterate不使用

四、 快取演算法

1、  LRU、LFU、FIFO

第25課 事務併發處理

一、 資料庫的隔離級別:併發性作用。

1、   ReadUncommited(未提交讀):沒有提交就可以讀取到資料(發出了Insert,但沒有commit就可以讀取到。)很少用

2、   ReadCommited(提交讀):只有提交後才可以讀,常用,

3、   RepeatableRead(可重複讀):mysql預設級別, 必需提交才能見到,讀取資料時資料被鎖住。

4、   Serialiazble(序列化讀):最高隔離級別,串型的,你操作完了,我才可以操作,併發性特別不好,

隔離級別

是否存在髒讀

是否存在不可重複讀

是否存在幻讀

Read Uncommitted(未提交讀)

Y

Y

Y

Read Commited(提交讀)

N

Y(可採用悲觀鎖解決)

Y

Repeatable Read(可重複讀)

N

N

Y

Serialiazble(序列化讀)

   








髒讀:沒有提交就可以讀取到資料稱為髒讀

不可重複讀:再重複讀一次,資料與你上的不一樣。稱不可重複讀。

幻讀:在查詢某一條件的資料,開始查詢的後,別人又加入或刪除些資料,再讀取時與原來的資料不一樣了。

1、   Mysql檢視資料庫隔離級別:

方法:select@@tx_isolation;


2、   Mysql資料庫修改隔離級別:

方法:set transactionisolation level 隔離級別名稱;


例如:修改為未提交讀:settransaction isolation level read uncommitted;


二、 事務概念(ACID)

ACID即:事務的原子性、一致性、獨立性及永續性 
事務的原子性:是指一個事務要麼全部執行,要麼不執行.也就是說一個事務不可能只執行了一半就停止了.比如你從取款機取錢,這個事務可以分成兩個步驟:1劃卡,2出錢.不可能劃了卡,而錢卻沒出來.這兩步必須同時完成.要麼就不完成.
事務的一致性:是指事務的執行並不改變資料庫中資料的一致性.例如,完整性約束了a+b=10,一個事務改變了a,那麼b也應該隨之改變.
事務的獨立性:是指兩個以上的事務不會出現交錯執行的狀態.因為這樣可能會導致資料不一致. 
事務的永續性:是指事務執行成功以後,就係統的更新是永久的.不會無緣無故的回滾.

三、 事務併發時可能出現問題

1、第一類丟失更新(Lost Update)

時間

  
   
   
   
   
   
   

第26課 hibernate悲觀鎖、樂觀鎖

Hibernate談到悲觀鎖、樂觀鎖,就要談到資料庫的併發問題,資料庫的隔離級別越高它的併發性就越差

         併發性:當前系統進行了序列化後,當前讀取資料後,別人查詢不了,看不了。稱為併發性不好

    資料庫隔離級別:見前面章級

一、 悲觀鎖

悲觀鎖:具有排他性(我鎖住當前資料後,別人看到不此資料)

悲觀鎖一般由資料機制來做到的。

1、   悲觀鎖的實現

通常依賴於資料庫機制,在整修過程中將資料鎖定,其它任何使用者都不能讀取或修改(如:必需我修改完之後,別人才可以修改)

2、   悲觀鎖的適用場景:

悲觀鎖一般適合短事務比較多(如某一資料取出後加1,立即釋放)

長事務佔有時間(如果佔有1個小時,那麼這個1小時別人就不可以使用這些資料),不常用。

3、   例項:


yon

使用者1、使用者2 同時讀取到資料,但是使用者2先 -200,這時資料庫裡的是800,現在使用者1也開始-200,可是使用者1剛才讀取到的資料是1000,現在使用者用剛剛一開始讀取的資料1000-200為800,而使用者1在更新時資料庫裡的是用房更新的資料800,按理說使用者1應該是800-200=600,而現在是800,這樣就造成的更新丟失。這種情況該如何處理呢,可採用兩種方法:悲觀鎖、樂觀鎖。先看看悲觀鎖:使用者1讀取資料後,用鎖將其讀取的資料鎖上,這時使用者2是讀取不到資料的,只有使用者1釋放鎖後使用者2才可以讀取,同樣使用者2讀取資料也鎖上。這樣就可以解決更新丟失的問題了。

實體類:

public class Inventory {

    private int itemNo;

    privateString itemName;   

    private int quantity;

    public intgetItemNo() {

        return itemNo;

    }

    public voidsetItemNo(intitemNo) {

        this.itemNo =itemNo;

    }

    publicString getItemName() {

        return itemName;

    }

    public voidsetItemName(String itemName) {

        this.itemName =itemName;

    }

    public intgetQuantity() {

        return quantity;

    }

    public voidsetQuantity(intquantity) {

        this.quantity =quantity;

    }  

}

對映檔案:

<hibernate-mapping>

    <class name="com.wjt276.hibernate.Inventory"table="t_inventory">

        <id name="itemNo">

            <generator class="native"/>

        </id>

        <property name="itemName"/>

        <property name="quantity"/>

    </class>

</hibernate-mapping>

4、   悲觀鎖的使用

如果需要使用悲觀鎖,肯定在載入資料時就要鎖住,通常採用資料庫的for update語句。

Hibernate使用Load進行悲觀鎖載入。

Session.load(Classarg0, Serializable arg1, LockMode arg2) throws HibernateException

LockMode:悲觀鎖模式(一般使用LockMode.UPGRADE)

session= HibernateUtils.getSession();

            tx = session.beginTransaction();

            Inventory inv =(Inventory)session.load(Inventory.class, 1,LockMode.UPGRADE);

            System.out.println(inv.getItemName());

            inv.setQuantity(inv.getQuantity()-200);

           

            session.update(inv);

            tx.commit();

5、   執行輸出SQL語句:

Hibernate:select inventory0_.itemNo as itemNo0_0_, inventory0_.itemName as itemName0_0_,inventory0_.quantity as quantity0_0_ from t_inventory inventory0_ where inventory0_.itemNo=?for update //在select語句中加入for update進行使用悲觀鎖。

腦白金

Hibernate:update t_inventory set itemName=?, quantity=? where itemNo=?

注:只有使用者釋放鎖後,別的使用者才可以讀取

注:如果使用悲觀鎖,那麼lazy(悚載入無效)

二、 樂觀鎖

樂觀鎖:不是鎖,是一種衝突檢測機制。

     樂觀鎖的併發性較好,因為我改的時候,別人隨邊修改。

     樂觀鎖的實現方式:常用的是版本的方式(每個資料表中有一個版本欄位version,某一個使用者更新資料後,版本號+1,另一個使用者修改後再+1,當使用者更新發現資料庫當前版本號與讀取資料時版本號不一致(等於小於資料庫當前版本號),則更新不了。

         Hibernate使用樂觀鎖需要在對映檔案中配置項才可生效。

實體類:

public classInventory {

    private int itemNo;

    privateString itemName;   

    private int quantity;  

    private int version;//Hibernate使用者實現版本方式樂觀鎖,但需要在對映檔案中配置

    public intgetItemNo() {

        return itemNo;

    }

    public voidsetItemNo(intitemNo) {

        this.itemNo =itemNo;

    }

    publicString getItemName() {

        return itemName;

    }

    public voidsetItemName(String itemName) {

        this.itemName =itemName;

    }

    public intgetQuantity() {

        return quantity;

    }

    public voidsetQuantity(intquantity) {

        this.quantity =quantity;

    }

    public intgetVersion() {

        return version;

    }

    public voidsetVersion(intversion) {

        this.version =version;

    }

}

對映檔案

<hibernate-mapping>

<!-- 對映實體類時,需要加入一個開啟樂觀鎖的屬性

optimistic-lock="version" 共有好幾種方式:

    - none  -version   - dirty - all

    同時需要在主鍵對映後面對映版本號欄位

    -->

<class name="com.wjt276.hibernate.Inventory"table="t_inventory"optimistic-lock="version">

        <id name="itemNo">

            <generator class="native"/>

        </id>

        <version name="version"/><!—必需配置在主鍵對映後面 -->

        <property name="itemName"/>

        <property name="quantity"/>

    </class>

</hibernate-mapping>

匯出輸出SQL語句:

createtable t_inventory (itemNo integer not null auto_increment, versioninteger not null, itemName varchar(255), quantity integer,primary key (itemNo))

注:新增的版本欄位version,還是我們來維護的,是由hibernate來維護的。

樂觀鎖在儲存資料時不用關心

相關文章