對象和對象之間除了繼承關系之外,還存在著關聯關系:包括分作一對一、一對多、多對一和多對多,由於這幾種關系在Kodo EJB中的實現原理基本類似,因此本文中主要就一對一類關聯關系進行深入的講述,同時通過簡單例子的分析和實踐詳細的說明如何使用Kodo EJB中提供的注釋來定義類和類之間的關聯關系,剩下的一對多、多對一和多對多三種關系將只在文章最後進行說明,請讀者參考一對一關系的實現過程。
面向對象的世界裡,類A和類B之間的一對一關系必須滿足如下條件:
對象A1引用了對象B1
類A其他對象An不可能應用同樣的對象B1
在關系數據庫中,我們通常使用唯一外鍵的方式來實現一對一關系,下面這個圖說明了這種的情況。
下面開始介紹一下Kodo EJB中和一對一關系實現相關的知識,為了說明的需要,我們首先定義一個虛擬的場景。
模擬場景
我們假定要完成一個圖書館管理系統,該系統中需要管理很多書,我們需要記錄書的基本信息如編號、書名、出版日期等基本信息,還需要記錄書的前言,序等信息。
假設我們根據上面的需求,將書設計成一個類(Book),包括了書的編號和名稱兩個屬性,同時將書的前言信息設計成另外一個類(BookExtend),它包括了書的編號和前言信息兩個屬性。由於一本書有前言而且也不可能有其他的書前言部分會和他一樣,所以類Book和BookExtend之間很自然的形成了一對一的關系。這兩個類的屬性以及類之間的關系如下圖所示。
[注]
1、為了說明的簡單,例子設計時每個對象僅僅選擇了必要的屬性。
2、上面的設計僅僅為了演示的要求而特意采用,不代表設計合理。
Kodo EJB中和一對一關系實現相關的內容
在Kodo EJB中,我們可以使用簡單的OneToOne注釋來聲明類和類之間的一對一關系,另外可選的,我們可以使用JoinColumn注釋來聲明兩個類對應的表之間使用什麼字段來進行關聯。
OneToOne
OneToOne注釋提供了5個屬性供開發者定義類和類之間一對一關系的細節內容。
targetEntity
Class類型的屬性。
定義關系類的類型,默認是該成員屬性對應的類類型,所以通常不需要提供定義。
mappedBy
String類型的屬性。
定義類之間的雙向關系。如果類之間是單向關系,不需要提供定義,如果類和類之間形成雙向關系,我們就需要使用這個屬性進行定義,否則可能引起數據一致性的問題。比如上面的演示場景中,我們只是定義Book類有BookExtend屬性,而BookExtend並沒有Book屬性,那麼他們是單向關系,如何BookExtend中也定義了Book屬性,那麼Book和BookExtend之間就構成了雙向關系。
cascade
CascadeType[]類型。
該屬性定義類和類之間的級聯關系。定義的級聯關系將被容器視為對當前類對象及其關聯類對象采取相同的操作,而且這種關系是遞歸調用的。舉個例子:Book和BookExtend有級聯關系,那麼刪除Book時將同時刪除它所對應的BookExtend對象。而如果BookExtend還和其他的對象之間有級聯關系,那麼這樣的操作會一直遞歸執行下去。
cascade的值只能從CascadeType.PERSIST(級聯新建)、CascadeType.REMOVE(級聯刪除)、CascadeType.REFRESH(級聯刷新)、CascadeType.MERGE(級聯更新)中選擇一個或多個。還有一個選擇是使用CascadeType.ALL,表示選擇全部四項。
fatch
FetchType類型的屬性。
可選擇項包括:FetchType.EAGER和FetchType.LAZY。前者表示關系類在主類加載的時候同時加載,後者表示關系類在被訪問時才加載。默認值是FetchType.EAGER。
optional
boolean類型的屬性。
定義該關聯類對是否必須存在。如果設置為false,那麼該屬性就不能設置為null。默認值是true。
OneToOne用法舉例
public class Book{
// 其他內容…
@OneToOne(optional=true,cascade=CascadeType.ALL)
public BookExtend bookExtend;
} JoinColumn
JoinColumn注釋用於定義主類在數據庫中對應的表通過什麼字段和關系類的主鍵進行關聯,這個注釋是可選的,如果不提供該注釋,Kodo在使用”對象名_ID”和關聯表進行關聯(簡單情況下),比如演示場景中類Book的bookExtend沒有使用JoinColumn注釋進行聲明,我們使用Kodo EJB提供的Mapping Tool工具生成表格的時候,Book類對應的表Book中將自動加入列bookExtend_ID,它的類型將和BookExtend對應表的主鍵字段id類型保持一致。
JoinColumn注釋中有兩個屬性:name和referencedColumnName屬性。
name
String類型。
它用於指定主類對應的表中和關系類的主鍵進行關聯的字段的名稱,比如上例中,我們不希望使用默認的bookExtend_ID字段名進行關聯,我們可以在Book類中使用JoinColumn注釋bookExtend屬性,設置JoinColumn注釋為自己想要的名字比如extendID或者其他。
referencedColumnName
String類型。
指定關聯表中與主表形成關聯關系的字段名。主要用於設置區別於主鍵字段的情況。比如BookExtend表中默認使用Id進行關聯,但現在需要使用其他字段進行關聯,我們就可以提供該屬性。
JoinColumn用法舉例
public class Book{
編寫符合要求的持久化類
// 其他內容…
@OneToOne(optional=true,cascade=CascadeType.ALL)
@JoinColumn(name="extendID",referencedColumnName="ID")
public BookExtend bookExtend;
}
現在我們開始根據上面章節中介紹的內容編寫符合模擬場景中要求的Book類和BookExtend類,下面是作者編寫的兩個類的全部代碼,代碼中加入了比較多的注釋方便大家理解。
BookExtend類
Book類 package org.vivianj.kodo.examples.beans;
import Javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
/**
* Book 用於表征系統中的書籍對象,他有三個屬性
* id - 書籍編號,書籍編號將由MySQL數據庫自動生成
* name - 書名
* bookExtend – 書的擴展信息,和BookExtend是一對一(OneToOne)關系
*/
/* Entity注釋表示該類是持久化類,的name屬性是該實體在查詢中對應的唯一名稱,默認是類名 */
@Entity(name = "Book")
/* Table注釋的name屬性指定該持久化類對應的數據表的名稱,默認數據表名和類名保持一致,為了
* 增強代碼的可移植性,建議大家在name屬性中使用大寫英文字母 */
/* Inheritance注釋的strategy確定了持久化對象和數據表之間的關系,可選擇項包括SINGLE_TABLE、JOINED和TABLE_PER_CLASS,我們這裡采用JOINED
*/
/* TABLE_PER_CLASS : strategy 設置為該選項表示每個類使用一個表,也就是上面所說的第一種情況*/
/* SINGLE_TABLE : strategy 設置為該選項表示所有類及其子類共用一個表,也就是上面所說的第二種情況*/
/* JOINED : strategy 設置為該選項表示每個類使用子表保存子類比父類多出的屬性,也就是上面所說的第三種情況*/
@Inheritance(strategy = InheritanceType.JOINED)
public class Book {
/* Id注釋表示該字段是標識字段 */
@Id
/* GeneratedValue注釋定義了該標識字段的產生方式,我們的演示系統中id由MySQL數據庫字段自動生成,因此選擇GenerationType.IDENTITY */
@GeneratedValue(strategy = GenerationType.IDENTITY)
/* Column注釋的name屬性定義了該類屬性對應的數據字段的名稱,為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字符 */
@Column(name = "ID")
public int id;
/* Basic注釋表示該屬性是基本屬性 */
@Basic
/* Column注釋的name屬性定義了該類屬性對應的數據字段的名稱,為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字符 */
@Column(name = "NAME")
public String name = null;
/* 使用OneToOne注釋表示該屬性和Book類形成一對一關系,OneToOne注釋的option屬性設為True表示該對象可以不存在,cascade屬性設置為CascadeType.ALL,表示Book和BookExtend對象級聯新建、更新、刪除、刷新 */
@OneToOne(optional=true,cascade=CascadeType.ALL)
/* 使用JoinColumn注釋設置兩個對象對應數據庫表之間的關聯字段 */
@JoinColumn(name="extendID",referencedColumnName="ID")
public BookExtend bookExtend;
}package org.vivianj.kodo.examples.beans;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
/**
* BookExtend 用於表征系統中書的擴展信息,他有兩個屬性:
* id - 擴展信息編號,擴展信息編號將由MySQL數據庫自動生成
* name - 書的前言信息
*/
/* Entity注釋表示該類是持久化類,的name屬性是該實體在查詢中對應的唯一名稱,默認是類名 */
@Entity
/* Table注釋的name屬性指定該持久化類對應的數據表的名稱,默認數據表名和類名保持一致,為了
* 增強代碼的可移植性,建議大家在name屬性中使用大寫英文字母 */
/* Inheritance注釋的strategy確定了持久化對象和數據表之間的關系,可選擇項包括
* SINGLE_TABLE、JOINED和TABLE_PER_CLASS,我們這裡采用JOINED
*/
/* TABLE_PER_CLASS : strategy 設置為該選項表示每個類使用一個表,也就是上面所說的第一種情況*/
/* SINGLE_TABLE : strategy 設置為該選項表示所有類及其子類共用一個表,也就是上面所說的第二種情況*/
/* JOINED : strategy 設置為該選項表示每個類使用子表保存子類比父類多出的屬性,也就是上面所說的第三種情況*/
@Inheritance(strategy = InheritanceType.JOINED)
public class BookExtend {
/* Id注釋表示該字段是標識字段 */
@Id
/* GeneratedValue注釋定義了該標識字段的產生方式,我們的演示系統中id由MySQL數據庫字段自動生成,因此選擇GenerationType.IDENTITY */
@GeneratedValue(strategy = GenerationType.IDENTITY)
/* Column注釋的name屬性定義了該類屬性對應的數據字段的名稱,為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字符 */
@Column(name = "ID")
public int id;
/* Basic注釋表示該屬性是基本屬性 */
@Basic
/* Column注釋的name屬性定義了該類屬性對應的數據字段的名稱,為了最大限度保持系統和數據庫之前的獨立性,建議使用大寫字符 */
@Column(name = "NAME")
public String name = null;
}
調用代碼
上面的代碼中,我們已經准備好了符合要求的持久化類,下面我們看看Kodo EJB中如何調用這兩個類完成Book類和BookExtend類的創建、修改、刪除工作。
由於篇幅的關系,這些沒有講述如何編譯、加強這些類並且准備相應的配置文件來完成整個項目開發環境的建立,這部分的內容請參考我的另外一篇文章《Kodo EJB:符合EJB3規范的持久層框架》
級聯新建對象
下面的這段代碼演示了只需要調用Book類的persist方法就同時持久化Book類對象和BookExtend類對象的情況。請注意其中用粗體標識出的部分。
/* 獲得EJB的實體管理器 */
EntityManagerFactory emf = Persistence.createEntityManagerFactory(null);
EntityManager em = emf
.createEntityManager(PersistenceContextType.EXTENDED);
/* 開始事務 */
em.getTransaction().begin();
/* 創建新的Book對象 */
Book book = new Book();
/* 設置Book對象的name屬性 */
book.name = "Kodo入門";
/* 創建新的BookExtend對象 */
BookExtend bookExtend = new BookExtend();
/* 設置對象屬性 */
bookExtend.name = "Spring is a very good book that ...";
/* 建立對象之間的關系 */
book.bookExtend = bookExtend;
/* 持久化對象,只需要持久化Book對象,不需要單獨持久化bookExtend對象 */
em.persist(book);
/* 結束事務 */
em.getTransaction().commit();
em.close();
emf.close();
級聯更新對象狀態
下面的這段代碼演示了只需要調用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 = “Kodo 入門”;
/* 創建新的BookExtend對象 */
BookExtend bookExtend = new BookExtend();
/* 設置對象屬性 */
bookExtend.id=1;
bookExtend.name = "Kodo 分為Kodo EJB和Kodo JDO ...";
/* 建立對象之間的關系 */
book.bookExtend = bookExtend;
/* 持久化對象,只需要調用Book對象的merge方法,不需要單獨處理bookExtend對象 */
em.merge(book);
/* 結束事務 */
em.getTransaction().commit();
em.close();
emf.close();/* 獲得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();
其他幾種關聯關系
一對多
我們可以使用OneToMany注釋來描述類和類之間的一對多關系,OneToMany注釋有四個屬性:targetEntity、mappedBy、cascade和fetch,這四個屬性的具體含義和OneToOne注釋注釋的同名屬性一一對應,請大家參考前面章節中的內容。
多對一
我們可以使用ManyToOne注釋來描述類和類之間的多對一關系,ManyToOne注釋有四個屬性:targetEntity、cascade、fetch和optional,這四個屬性的具體含義和OneToOne注釋注釋的同名屬性一一對應,請大家參考前面章節中的內容。
多對多
我們可以使用ManyToMany注釋來描述類和類之間的多對多關系,ManyToMany注釋有四個屬性:targetEntity、mappedBy、cascade和fetch,這四個屬性的具體含義和OneToOne注釋注釋的同名屬性一一對應,請大家參考前面章節中的內容。
總結
對象和對象之間除了繼承關系之外,還存在著關聯關系,包括一對一、一對多、多對一和多對多的關系,本文中,作者以一對一關系為例,結合簡單的例子,詳細地描述了如何在Kodo EJB框架下通過注釋簡單的描述類和類之間的關系,並且實現類操作的級聯特性。文章的最後簡單的說明了Kodo EJB中實現一對多、多對一和多對多時需要用到的注釋,具體的實現請大家參考文檔中的內容自行完成。