程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用Apache OpenJPA開發EJB 3.0應用,第4部分: 實體關聯

使用Apache OpenJPA開發EJB 3.0應用,第4部分: 實體關聯

編輯:關於JAVA

對象和對象之間除了繼承關系之外,還存在著關聯關系:包括一對一、一對多 、多對一和多對多關系,在 OpenJPA 框架下,開發者只需要使用 javax.persistence.OneToOne 這樣的注釋,並提供相應的配置內容,就可以輕松 的實現實體之間的關聯關系,並且能夠實現實體的級聯創建、更新和刪除。

本文中我們將以實體之間的一對一關聯關系為例,深入地講述如何使用 OpenJPA 框架提供的注釋,實現企業應用中實體之間的關聯關系。文中將提供一 個簡單的例子,詳細的說明如何定義類和類之間的一對一關聯關系的步驟,同時 會重點講述這些注釋所支持的屬性。一對多、多對一和多對多這三種關聯關系在 OpenJPA 中的實現過程和一對一關聯關系的實現過程是一致的,只是需要選擇使 用不同的注釋,在本文的最後,會對實現這三種關聯關系進行簡單說明,讀者可 以參考一對一關系的實現過程來實現一對多、多對一和多對多的關聯關系。

一對一關系

在面向對象的世界裡,類 A 和類 B 之間形成一對一關系必須滿足如下條件:

對象 A1 引用了對象 B1;

類 A 的其它對象 An 不能引用同樣的對象 B1。

在關系數據庫中,我們通常使用唯一外鍵的方式來實現一對一關系,下面這個 圖說明了這種的情況。

圖 1. 關系數據庫中的一對一關系

下面開始介紹 OpenJPA 中實現實體之間一對一關聯關系的相關知識,為了說 明的需要,我們首先定義一個簡單的應用場景。

模擬場景

假定開發者要完成一個圖書館管理系統,我們需要記錄書的基本信息如編號、 書名、出版日期等基本信息,還需要記錄書的前言,序等信息。

為了說明實體之間的一對一關系,我們將書設計成一個類(Book),包括書的 編號和名稱兩個屬性,同時將書的前言設計成另外一個類(BookExtend),它包 括書的編號和前言兩個屬性。由於一本書有前言而且也不可能有其它書的前言部 分會和它一樣,所以類 Book 和 BookExtend 之間很自然的形成了一對一的關系 。這兩個類的屬性以及類之間的關系如下圖所示。

圖 2. 類之間的一對一關系

[注]:為了說明的簡單,本例子設計時每個對象只選擇了必要的屬性。

描述一對一關系

在 OpenJPA 中,開發者用來描述實體之間一對一關系時可選擇的注釋包括 javax.persistence.OneToOne 和 javax.persistence.JoinColumn。其中 javax.persistence.OneToOne 注釋是必須使用的,它被用來聲明類和類之間存在 著一對一關系,javax.persistence.JoinColumn 注釋是可選的,開發者使用 JoinColumn 注釋來聲明兩個類在數據庫中對應的表之間關聯時的細節,包括主表 中關聯字段的名稱、從表中使用什麼字段來進行關聯等。

javax.persistence.OneToOne

javax.persistence.OneToOne 注釋支持如下 5 個屬性,它們可以被開發者用 來定義實體和實體之間一對一關聯關系的細節內容。

target Entity

targetEntity 屬性是 Class 類型的屬性。定義實體一對一關系中處於從屬地 位的實體類的類型。如果沒有為該屬性設置值,OpenJPA 容器默認 targetEntity 屬性的值是該成員屬性對應的類類型,所以實體關系定義時通常不需要為 targetEntity 屬性設置值。

mappedBy

mappedBy 屬性是 String 類型的屬性。mappedBy 屬性的值是當前實體在關聯 實體中的屬性名稱,使用 mappedBy 可以定義實體類之間的雙向關系。如果類之 間是單向關系,不需要提供定義,如果類和類之間形成雙向關系,我們就需要使 用這個屬性進行定義,否則可能引起數據一致性的問題。

以演示場景中 Book 和 BookExtend 實體為例,假設我們只定義 Book 類有 BookExtend 類型的屬性,而 BookExtend 並沒有 Book 類型的屬性,那麼說明 Book 和 BookExtend 實體之間是單向關系;如果 BookExtend 中也定義了 Book 屬性,那麼 Book 和 BookExtend 實體之間就構成了雙向關系。

cascade

cascade 屬性的類型是 CascadeType[] 類型。cascade 屬性定義實體和實體 之間的級聯關系。使用 cascade 屬性定義的級聯關系將被容器視為對當前類對象 及其關聯類對象采取相同的操作,而且這種關系是遞歸調用的。

以演示場景中 Book 和 BookExtend 實體為例:如果設置 Book 和 BookExtend 存在級聯關系,那麼刪除 Book 時將同時刪除它所對應的 BookExtend 對象。而如果 BookExtend 還和其它的對象之間有級聯關系,那麼這 樣的操作會一直遞歸執行下去。

cascade 的值只能從 CascadeType.PERSIST(級聯新建)、 CascadeType.REMOVE(級聯刪除)、CascadeType.REFRESH(級聯刷新)、 CascadeType.MERGE(級聯更新)中選擇一個或多個。還有一個更方便的選擇是使 用 CascadeType.ALL,表示選擇上面全部四項。

fetch

fetch 屬性是 FetchType 類型的屬性。可選擇項包括:FetchType.EAGER 和 FetchType.LAZY。前者表示關聯關系的從類在主類加載的時候同時加載,後者表 示關聯關系的從類在自己被訪問時才加載。默認值是 FetchType.EAGER。

optional

optional 屬性是 boolean 類型的屬性。optional 屬性用於定義關聯關系的 從類對象是否必須存在。如果設置為 false,那麼該屬性就不能設置為 null。默 認值是 true。

javax.persistence.OneToOne 用法舉例

public class Book {
  // 其它實體映射內容…
  /*
  * 使用 OneToOne 注釋表示該屬性和 Book 類形成一對一關系, OneToOne
  * 注釋的 option 屬性設為 True 表示該對象可以不存在,cascade  屬性
  * 設置為 CascadeType.ALL,表示 Book 和 BookExtend 對象級聯新 建、 更新、刪除、刷新
  */
  @OneToOne(optional=true,cascade=CascadeType.ALL)
  public BookExtend bookExtend;
}

javax.persistence.JoinColumn

javax.persistence.JoinColumn 注釋可以和 javax.persistence.OneToOne 注釋一起使用,用於定義關聯關系中的主類在數據庫中對應的表通過什麼字段和 關聯關系中的從類的主鍵進行關聯,這個注釋是可選的,如果不提供該注釋, OpenJPA 會默認使用”對象名_ID”和關聯表的主鍵字段進行關聯。

以演示場景中 Book 和 BookExtend 實體為例:如果 Book 的 bookExtend 屬 性沒有使用 javax.persistence.JoinColumn 注釋進行聲明,我們使用 OpenJPA 提供的 Mapping Tool 工具生成表格的時候,Book 類對應的表 Book 中將自動加 入列 bookExtend_ID,它的類型將和 BookExtend 對應表的主鍵字段id類型保持 一致。

JoinColumn 注釋支持兩個重要屬性:name 和 referencedColumnName 屬性。

name

name 屬性的類型是 String 類型。name 屬性用於指定關聯關系中的主類對應 的表中和關聯關系中的從類的主鍵進行關聯的字段的名稱。以演示場景中 Book 和 BookExtend 實體的關系為例:如果 Book 實體對應的表使用“beID”字段和 BookExtend 實體對應表的主鍵進行對應,我們可以在 Book 類中為 bookExtend 屬性提供 javax.persistence.JoinColumn 注釋,設置它的 name 屬性為“beID ”。

referencedColumnName

referencedColumnName 屬性的類型是 String 類型。referencedColumnName 屬性指定關聯關系中的從類與關聯關系中的主類對應的表之間形成關聯關系的字 段名稱,通常用於關聯關系中的從類的關聯字段不是自己的主鍵的情況。以演示 場景中 Book 和 BookExtend 實體的關系為例:BookExtend 表中默認使用 Id 字 段和 Book 類的某個字段進行關聯,但如果實際情況下 BookExtends 表需要使用 “myID“字段和 Book 表進行關聯,我們就可以設置 javax.persistence.JoinColumn 注釋的屬性值為“myID”。

javax.persistence.JoinColumn 用法舉例

public class Book {
  // 其它內容…

  /*
  * 使用 OneToOne 注釋表示該屬性和 Book 類形成一對一關系, OneToOne
  * 注釋的 option 屬性設為 True 表示該對象可以不存在,cascade  屬性
  * 設置為 CascadeType.ALL,表示 Book 和 BookExtend 對象級聯新 建、 更新、刪除、刷新
  */
  @OneToOne(optional = true, cascade = CascadeType.ALL)
  /*
  * 使用 JoinColumn 注釋設置兩個對象對應數據庫表之間的關聯字段
  * name 屬性指定關聯關系中主類對應表中參與關聯關系的字段名稱,
  * referencedColumnNam 屬性指定關聯關系中從類對應表中參與關
  * 聯關系的字段名稱,
  */
  @JoinColumn(name = "beID", referencedColumnName = "myID")
  public BookExtend bookExtend;
}

根據模擬場景的需求,結合我們前面學習到的描述實體之間一對一關聯關系的 知識,我們可以采用如下設計:

Book 類和 BookExtend 之間存在一對一關聯關系;

Book、BookExtend 對應的表的主鍵字段由 MySQL 自動生成;

Book 表中參與關聯關系的字段名為“beID“;

BookExtend 表中參與關聯關系的字段使用默認字段“ID”;

Book 類和 BookExtend 類之間存在全部級聯關系;

不是每一個 Book 對象都需要有對應的 BookExtend 對象。

根據這樣的設計,我們可以開始編寫實體 Book 和 BookExtend 對應的持久化 類代碼,下面是作者編寫的兩個實體類的全部代碼,大家可以參考代碼中加入的 大量注釋學習如何使用注釋來描述實體和實體之間的一對一關聯關系。

Book 類

1. package org.vivianj.openjpa.beans;
2.
3. import javax.persistence.Basic;
4. import javax.persistence.CascadeType;
5. import javax.persistence.Column;
6. import javax.persistence.Entity;
7. import javax.persistence.GeneratedValue;
8. import javax.persistence.GenerationType;
9. import javax.persistence.Id;
10. import javax.persistence.Inheritance;
11. import javax.persistence.InheritanceType;
12. import javax.persistence.JoinColumn;
13. import javax.persistence.OneToOne;
14.
15. /**
16. * Book 用於表征系統中的書籍對象,它有三個屬性 id - 書籍編號 ,
    * 書籍編號將由 MySQL 數據庫自動生成 name - 書名  bookExtend –
17. * 書的擴展信息,和 BookExtend 是一對一(OneToOne)關系 
18. */
19.
20. @Entity(name = "Book")
21. public class Book {
22. /* Id 注釋表示該字段是標識字段 */
23. @Id
24. /*
25.  * GeneratedValue 注釋定義了該標識字段的產生方式,我們的演示 系統中
26.  * id 由 MySQL 數據庫字段自動生成,因此選擇  GenerationType.IDENTITY
27.  */
28. @GeneratedValue(strategy = GenerationType.IDENTITY)
29. /*
30.  * Column 注釋的 name 屬性定義了該類屬性對應的數據字段的名 稱,
     * 為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字 符
31.  */
32. @Column(name = "ID")
33. public int id;
34.
35. /* Basic 注釋表示該屬性是基本屬性 */
36. @Basic
37. /*
38.  * Column 注釋的 name 屬性定義了該類屬性對應的數據字段的名 稱,
     * 為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字 符
39.  */
40. @Column(name = "NAME")
41. public String name = null;
42.
43. /*
44.  * 使用 OneToOne 注釋表示該屬性和 Book 類形成一對一關系, OneToOne
45.  * 注釋的 option 屬性設為 True 表示該對象可以不存在, cascade 屬性
46.  * 設置為 CascadeType.ALL,表示 Book 和 BookExtend 對象級 聯新建、 更新、刪除、刷新
47.  */
48. @OneToOne(optional = true, cascade = CascadeType.ALL)
49. /* 使用 JoinColumn 注釋設置兩個對象對應數據庫表之間的關聯字段  */
50. @JoinColumn(name = "extendID")
51. public BookExtend bookExtend;
52. }

BookExtend 類

1. package org.vivianj.openjpa.beans;
2.
3. import javax.persistence.Basic;
4. import javax.persistence.Column;
5. import javax.persistence.Entity;
6. import javax.persistence.GeneratedValue;
7. import javax.persistence.GenerationType;
8. import javax.persistence.Id;
9. import javax.persistence.Inheritance;
10. import javax.persistence.InheritanceType;
11.
12. /**
13. * BookExtend 用於表征系統中書的擴展信息,它有兩個屬性:
    * id - 擴展信息編號,擴展信息編號將由 MySQL 數據庫自動生 成
14. * name -書的前言信息
15. */

16. @Entity
17. public class BookExtend {
18. /* Id 注釋表示該字段是標識字段 */
19. @Id
20. /*
21.  * GeneratedValue 注釋定義了該標識字段的產生方式,我們的演示 系統中
22.  * id 由 MySQL 數據庫字段自動生成,因此選擇  GenerationType.IDENTITY
23.  */
24. @GeneratedValue(strategy = GenerationType.IDENTITY)
25. /*
26.  * Column 注釋的 name 屬性定義了該類屬性對應的數據字段的名 稱,
     * 為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字 符
27.  */
28. @Column(name = "ID")
29. public int id;
30.
31. /* Basic 注釋表示該屬性是基本屬性 */
32. @Basic
33. /*
34.  * Column 注釋的 name 屬性定義了該類屬性對應的數據字段的名 稱,
     * 為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字 符
35.  */
36. @Column(name = "NAME")
37. public String name = null;
38. }

調用代碼

上面的代碼中,我們已經准備好了符合要求的持久化類,下面我們看看 OpenJPA 中如何調用這兩個類完成 Book 類和 BookExtend 類的創建、修改、刪 除工作。

由於篇幅的關系,這些沒有講述如何編譯、加強這些類並且准備相應的配置文 件來完成整個項目開發環境的建立,這部分的內容請參考另外一篇文章《OpenJPA :符合 EJB3 規范的持久層框架》。

級聯新建對象

下面的這段代碼演示了只需要調用 Book 類的 persist 方法就同時持久化 Book 類對象和 BookExtend 類對象的情況。請注意其中用粗體標識出的部分。

/* 獲得 EJB 的實體管理器 */
EntityManagerFactory emf = Persistence.createEntityManagerFactory (null);
EntityManager em = emf.createEntityManager (PersistenceContextType.EXTENDED);
/* 開始事務 */
em.getTransaction().begin();

/* 創建新的 BookExtend 對象 */
BookExtend bookExtend = new BookExtend();
/* 設置對象屬性 */
bookExtend.name = "前言 本書重點說明了...";
/* 創建新的 Book 對象 */
Book book = new Book();
/* 設置 Book 對象的 name 屬性 */
book.name = "<<Web Services實踐>>";
/* 建立對象之間的關系 */
book.bookExtend = bookExtend;

/* 持久化對象,只需要持久化 Book 對象,不需要單獨持久化 bookExtend  對象 */
em.persist(book);

/* 結束事務 */
em.getTransaction().commit();
em.close();
emf.close();

產生的 SQL 語句

下面的這段 SQL 語句是運行上面的代碼時 OpenJPA 自動生成的,我們可以從 中看到 OpenJPA 級聯新建對象時的處理過程:

-- 創建 Book 實體對應的數據庫表 --
CREATE TABLE Book (ID INTEGER NOT NULL AUTO_INCREMENT, NME  VARCHAR(255),
  extendID INTEGER, PRIMARY KEY (ID));
-- 創建 BookExtend 實體對應數據庫表 --
CREATE TABLE BookExtend (ID INTEGER NOT NULL  AUTO_INCREMET,
  NAME VARCHAR(255), PRIMARY KEY (ID)) ;
-- 將 Book 實體對象插入數據庫中 --
INSERT INTO Book (NAME) VALUES (‘<<Web Services實踐 >>’)
-- 獲取 Book 實體對象的編號 --
SELECT LAST_INSERT_ID();
-- 將 BookExtend 實體對象插入數據庫中 --
INSERT INTO BookExtend (NAME) VALUES (‘前言 本書重點說明了... ’) ;
--獲取 BookExtend 實體對象的編號 --
SELECT LAST_INSERT_ID();
-- 將 BookExtend 實體對象的編號更新到Book表中形成關聯關系 --
UPDATE Book SET extendID = 1 WHERE ID = 1;

級聯更新對象狀態

下面的這段代碼演示了只需要調用 Book 類的 merge 方法就同時更新 Book 類對象和 BookExtend 類對象狀態的情況。請注意其中用粗體標識出的部分。

/* 獲得 EJB 的實體管理器 */
EntityManagerFactory emf = Persistence.createEntityManagerFactory (null);
EntityManager em = emf.createEntityManager (PersistenceContextType.EXTENDED);
/* 開始事務 */
em.getTransaction().begin();

/* 創建新的 Book 對象 */
Book book = new Book();
/* 設置 Book 對象的 id 屬性 */
book.id= 1;
book.name = “OpenJPA入門”;
/* 創建新的 BookExtend 對象 */
BookExtend bookExtend = new BookExtend();
/* 設置對象屬性 */
bookExtend.id=1;
bookExtend.name = "OpenJPA開發EJB3.0應用 ...";
/* 建立對象之間的關系 */
book.bookExtend = bookExtend;

/* 持久化對象,只需要調用 Book 對象的 merge 方法,不需要單獨處理  bookExtend 對象 */
em.merge(book);

/* 結束事務 */
em.getTransaction().commit();
em.close();
emf.close();

級聯刪除對象

下面的這段代碼演示了只需要通過 Query 對象,就可以在刪除 Book 類的同 時,刪除它對應的 BookExtend 實體對象的情況。請注意其中用粗體標識出的部 分。

/* 獲得 EJB 的實體管理器 */
EntityManagerFactory emf = Persistence.createEntityManagerFactory (null);
EntityManager em = emf.createEntityManager (PersistenceContextType.EXTENDED);
/* 開始事務 */
em.getTransaction().begin();
/* 使用查詢刪除對象,可以不必將對象加入到內存中,提高效率 */
Query q = entityManager.createQuery("delete from Book c WHERE  c.id=:id");
int id = book.id;
/* 設置被刪除 Book 對象的主鍵值 */
q.setParameter("id", id);
/* 當方法被調用時,Book 對象對應的 BookExtend 對象會同時被刪除  */
q.executeUpdate();

/* 結束事務 */
em.getTransaction().commit();
em.close();
emf.close();

其它幾種關聯關系

在上面的文章中我們學習了如何在 OpenJPA 中通過 javax.persistence.OneToOne 注釋和 javax.persistence.JoinColumn 注釋實現 實體之間的一對一關聯關系。在企業應用中,除了一對一關聯關系,實體之間還 可能存在一對多、多對一、多對多等關聯關系,不過在 OpenJPA 容器中,這些實 體之間關聯關系的實現都大同小異,只是需要開發者選擇使用不同的注釋。

開發者用來描述實體之間一對多關聯關系的注釋是 javax.persistence.OneToMany 注釋,用來描述實體之間多對一關聯關系的注釋 是 javax.persistence.OneToMany 注釋,用來描述實體之間多對多關聯關系的注 釋是 javax.persistence.ManyToMany 注釋。這三個注釋都支持 targetEntity、 mappedBy、cascade 和 fetch 這四個屬性,這些屬性的具體含義和 OneToOne 注 釋注釋的同名屬性一一對應,請大家參考前面章節中的內容。

javax.persistence.OneToMany、 javax.persistence.OneToMany、 javax.persistence.ManyToMany 這三個注釋都可以和 javax.persistence.JoinColumns 注釋一起使用, javax.persistence.JoinColumns 注釋的作用是為一對多、多對一、多對多關聯 關系在數據庫中的體現提供更多細節描述。javax.persistence.JoinColumns 注 釋中可以包含多個 javax.persistence.JoinColumn 注釋的內容, javax.persistence.JoinColumn 注釋的屬性請參考本文前面部分的描述。

總結

對象和對象之間除了繼承關系之外,還存在著關聯關系,包括一對一、一對多 、多對一和多對多的關系,本文中,作者以實體之間的一對一關聯關系為例,結 合企業應用中的實際例子,詳細地描述了如何在 OpenJPA 框架下通過注釋簡單的 描述實體和實體之間的關聯關系,並且實現實體的級聯操作。文章的最後簡單的 介紹了 OpenJPA 中實現實體之間一對多、多對一和多對多關聯關系時需要用到的 注釋,這些注釋的用法和描述一對一關聯關系時大體一致,如何通過描述實現實 體之間一對多、多對一和多對多關聯關系請大家參考本文中的內容自行完成。

原文:http://www.ibm.com/developerworks/cn/java/j-lo- openjpa4/index.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved