Cayenne 是一個 Apache 下的持久層框架項目, 作用與 Hibernate / iBATIS 之類的持久層框架類似.
- 本文譯自 Cayenne 3.1版的官方文檔 ( Cayenne Getting Started Guide, https://cayenne.apache.org/docs/3.1/getting-started-guide/ )
- 譯文的目的是為初學者快速了解 Cayenne 減少一些語言障礙. 若要深入研究和使用 Cayenne 還是建議直接閱讀官方原版文檔.
- 譯者水平有限, 翻譯不準確的地方還望斧正.另附對Cayenne的相關(guān)知識闡述更全面的 Cayenne Guide 鏈接: https://cayenne.apache.org/docs/3.1/cayenne-guide/
1. 配置環(huán)境
本章的目標是安裝(或檢查您是否已安裝)構(gòu)建Cayenne應(yīng)用程序所需的最低軟件環(huán)境.
1.1 安裝Java
顯然,JDK 必須安裝. Cayenne 3.1需要 JDK 1.5 或 更高版本.
1.2 安裝Eclipse IDE 和 Maven插件
譯注: Maven是一個項目管理工具, 本教程主要使用它來進行項目所依賴的 jar 的管理(自動下載)
下載Eclipse. 本教程基于Galileo JEE版本的Eclipse(Eclipse 3.5), 但它同樣適用于最新的其它Eclipse通用版本. Eclipse下載完后, 解壓并運行之.
對本教程而言, 你唯一需要安裝的插件是m2eclipse.
選擇Eclipse菜單 "Help > Install New Software", 然后點擊 "Add…" 添加一個新的下載站點(download site), 在 "Name" 輸入框中輸入 "Maven", "Location"框中輸入http://m2eclipse.sonatype.org/sites/m2e.
你可以選擇任何你想要的可選組件, 但是對本教程而言, 你只需要選擇最少的基本組件即可, 如下圖:
[站外圖片上傳中...(image-9f7265-1524231454453)]
按照Eclipse對話框中的提示, 完成安裝.
2. 映射(mapping)基礎(chǔ)
2.1. 開始一個項目
本章目標是創(chuàng)建一個包含基本Cayenne映射(Cayenne mapping)的Java項目. 其中展示 CayenneModeler 圖形化工具的使用,
演示如何創(chuàng)建初始的映射對象: DataDomain
, DataNode
, DataMap
.
譯注: CayenneModeler 是 Cayenne的一個圖形化建模工具, 使用此工具可比較直觀且自動化地創(chuàng)建 Cayenne 持久層對象模型所需各類文件
在Eclipse中創(chuàng)建一個新項目
在Eclipse中選擇 "File > New > Other…?", "Maven > Maven Project".
點擊 "Next".
在接下來的界面中選中 "Create a simple project" 復(fù)選框, 再次點擊 "Next".
對話框中顯示如下圖所示內(nèi)容, 填寫 "Group Id" 和 "Artifact Id" 并點擊 "Finish".
現(xiàn)在, 在Eclipse的workspace里應(yīng)該有一個空的項目. 檢查一下這個項目的Java編譯設(shè)置是否正確.
右鍵單擊 "tutorial" 項目, 選擇 "Properties > Java Compiler", 確保 "Compiler compliance level"
至少為 "1.5" (一些版本的Maven插件似乎會默認將其設(shè)置為1.4)
下載并運行CayenneModeler
盡管在本教程中稍后我們將使用Maven來導(dǎo)入Cayenne的運行時所需的jar文件到項目中, 你仍然需要下載Cayenne 以便可以使用 CayenneModeler 工具.
如果你直接使用Maven, 你也可以從Maven直接啟動CayenneModeler. 這里我們使用更傳統(tǒng)的文件來做.
下載最新的發(fā)布版. 解壓到任意位置, 根據(jù)特定操作系統(tǒng)平臺的要求啟動 CayenneModeler.
在大多數(shù)平臺下, 只需要簡單地雙擊 Modeler 的圖標即可.
Modeler的歡迎界面如下圖:
[站外圖片上傳中...(image-c9030e-1524231454453)]
在 CayenneModeler 中創(chuàng)建一個新的映射項目(Mapping Project)
在歡迎界面中點擊 New Project
按鈕即會出現(xiàn)一個新的 Mapping 項目, 并包含一個DataDomain.
DataDomain 的含意將會在本教程的其他地方解釋.
現(xiàn)在你只需要知道 DataDomain 是你的Mapping項目的根.
創(chuàng)建一個DataNode
你需要創(chuàng)建的下一個項目對象是 DataNode.
DataNode 是你應(yīng)用程序?qū)⒁B接的單個數(shù)據(jù)庫的描述.
Cayenne 的 mapping 項目可應(yīng)用于多于一個數(shù)據(jù)庫的情況, 但是現(xiàn)在我們僅使用一個數(shù)據(jù)庫.
選中左側(cè)的 "project", 點擊工具欄上的 Create DataNode
按鈕 ( 或者在菜單中選擇Project > Create DateNode
), 一個新的DataNode就出現(xiàn)了.
現(xiàn)在你需要指定JDBC連接參數(shù).
如果使用內(nèi)存型數(shù)據(jù)庫 Derby, 那你可以輸入如下的配置:
JDBC Driver: org.apache.derby.jdbc.EmbeddedDriver
DB URL: jdbc:derby:memory:testdb;create=true
這里我們創(chuàng)建了一個內(nèi)存型數(shù)據(jù)庫(in-memory database). 因此, 當你停止你的程序時, 所有的數(shù)據(jù)將會丟失. 在更多實際的項目中, 你應(yīng)該會連接一個實際將數(shù)據(jù)存儲于磁盤的數(shù)據(jù)庫, 但是對于這個簡單的教程而言, 我們將使用內(nèi)存數(shù)據(jù)庫.
譯注: 與傳統(tǒng)的數(shù)據(jù)庫(如mysql)不同, 內(nèi)存型數(shù)據(jù)庫可直接將數(shù)據(jù)加載到內(nèi)存中來運行, 可理解為一個直接在內(nèi)存中運行的關(guān)系型數(shù)據(jù)庫. 本教程使用 Derby, 并在 DB URL 處配置 create=true, 這樣可根據(jù) CayenneModeler 建立的模型來自動生成數(shù)據(jù)庫.
同時, 你需要更改 "Schema Update Strategy".
在下拉列框中選擇 org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy
,
這樣當程序啟動時, Cayenne 將會根據(jù)對象關(guān)系模型映射(ORM mapping)信息在Derby中創(chuàng)建一個新的數(shù)據(jù)庫模式(Schema).
[站外圖片上傳中...(image-798b88-1524231454453)]
創(chuàng)建一個DataMap
現(xiàn)在, 你將要創(chuàng)建一個 DataMap.
DataMap 是一個包含了所有映射信息的對象. 點擊工具欄上的 "Create DataMap" 按鈕 (或選擇相應(yīng)的菜單項).
注意, 新創(chuàng)建的 DataMap 將自動關(guān)聯(lián)到上一步驟中創(chuàng)建的 DataNode. 如果有多于一個DataNode, 你應(yīng)該手動關(guān)聯(lián) DataMap 到正確的 DataNode. 也就是說, 在 DataDomain 中的一個 DataMap 必須通過指定關(guān)聯(lián)來指向一個數(shù)據(jù)庫描述.
在DataMap的配置中, 除了 "Java Package", 你都可以保留DataMap的默認配置.
在 "Java Package" 框中輸入 "org.example.cayenne.persistent". 這個包名將在隨后應(yīng)用于所有的持久層類.
[站外圖片上傳中...(image-fd953-1524231454453)]
保存項目
在你進行實際的映射配置之前, 讓我們先保存一下這個項目.
點擊工具欄上的 "Save" 按鈕, 并指定保存路徑到本章前面創(chuàng)建的名為 "tutorial" 的 Eclipse 項目的文件夾中的子文件夾 "src/main/resources", 并將項目保存在這里.
現(xiàn)在, 回到 Eclipse, 右鍵點擊 "tutorial" 項目, 并選擇 "Refresh(刷新)", 你將看到3個 Cayenne 的 XML 文件. ( 譯注: 原文為3個, 但我只看到2個)
[站外圖片上傳中...(image-130a6f-1524231454453)]
注意, XML文件的存放位置不是隨意的. Cayenne 運行時將在應(yīng)用程序的 CLASSPATH
尋找 cayenne-*.xml
文件.
src/main/resources
文件夾應(yīng)成為Eclipse中我們項目的"class folder".
( 如果我們以命令行方式使用Maven, 那上述位置也是 Maven 復(fù)制 jar 文件的標準目標位置 )
譯注: 按前面一段的步驟保存項目文件到 "src/main/resources" 就行了, 對于 Eclipse 中創(chuàng)建的 Maven 項目 "src/main/resources" 默認就是在CLASSPATH中的. 如果你沒有使用 Maven, 可直接保存到 src 根目錄即可.
2.2. 對象關(guān)系映射入門(ORM)
本節(jié)的目標是學會怎么使用 CayenneModeler 來創(chuàng)建一個簡單的對象關(guān)系模型 ( Object-Relational model, ORM ).
我們將為以下數(shù)據(jù)庫模式創(chuàng)建一個完整的 ORM 模型:
[站外圖片上傳中...(image-a94d08-1524231454453)]
通常情況下, 你已經(jīng)有創(chuàng)建好了的數(shù)據(jù)庫, 那你可以通過菜單 "Tools > Reengineer Database Schema" 將其快速導(dǎo)入到 Cayenne 中. 相比手工映射, 這將節(jié)省你很多時間. 但是, 懂得如何手工創(chuàng)建映射同樣重要,
因此, 我們下面將演示手工操作的方法.
映射數(shù)據(jù)庫表和列
讓我們回到 CayenneModeler, 打開新我們新創(chuàng)建的項目, 并開始添加 ARTIST 表.
在 Cayenne 映射中, 數(shù)據(jù)庫表被稱作 DbEntities ( 可以是實際的表或視圖 ).
在左邊項目樹中選中 "datamap", 點擊工具欄上的 "Create DbEntity" 按鈕 ( 或使用菜單 "Project > Create DbEntity" ), 一個新的 DbEntity 即被創(chuàng)建出來了.
在 "DbEntity Name" 字段輸入 "ARTIST". 然后點擊實體工具欄(entity toolbar, 譯注:就是下圖右側(cè)詳情區(qū)域上方的二級工具欄 ) 上的 "Create Attribute" 按鈕切換到 "Attribute" 標簽頁, 并新增一個名叫 "untitledAttr" 的屬性( Attribute, 這里的 attribute 對應(yīng)一個表中的列 ).
讓我們將其重命名為ID, 并設(shè)置為 INTEGER
和 'PK' ( 主鍵 ):
[站外圖片上傳中...(image-317777-1524231454453)]
類似地, 增加 NAME VARCHAR(200)
和 DATE_OF_BIRTH DATE
屬性.
然后, 重復(fù)上述過程, 創(chuàng)建如前面數(shù)據(jù)庫模式圖中所示的 PAINTING 和 GALLERY 實體.
不要忘記定期保存你的項目, 以免丟失你所做的工作.
因為 Eclipse 默認情況下并不會自動感知建模工具中所做的修改, 所以, 每次 CayenneModeler 保存后,
你都應(yīng)該在 Eclipse 中刷新項目.
映射數(shù)據(jù)庫關(guān)系
現(xiàn)在我們需要指定 ARTIST, PAINTING 和 GALLERY 表之間的 ( 外鍵 ) 關(guān)系.
首先創(chuàng)建一對多的 ARTIST/PAINTING 關(guān)系:
選擇左邊的 ARTIST DbEntity, 并點擊 "Relationship" 標簽頁
點擊實體工具欄上的 "Create Relationship" 按鈕 [站外圖片上傳中...(image-14881-1524231454453)], 一個名為 "untitledRel" 的關(guān)系即被創(chuàng)建出來.
選擇 "Target" 為 "PAINTING"
點擊右側(cè)工具欄上的 "Database Mapping" 按鈕, 即會彈出關(guān)系配置對話框.
在這里你可以給關(guān)系取個名字, 同樣也可以給反向關(guān)系取名. 這個名字可以任取 ( 這實際上是數(shù)據(jù)庫參考約束的一個符號名稱 ), 但是, 更推薦使用 Java 標識符, 因為稍后這個名字將被以同樣的拼寫方式保存下來. 我們將這個關(guān)系稱作"paintings", 反向
關(guān)系稱作 "artist".
譯注: 這里的關(guān)系即 ARTIST 表和 PAINTING 表之間一對多的外鍵約束, 關(guān)系名稱即對于一個 ARTIST 來說 PAINTING 表的數(shù)據(jù)是它的什么, 而反向關(guān)系即: 對于一個 PAINTING 來說, ARTIST 是它的什么. 呵呵, 有點繞~ 簡單說, 對于一個藝術(shù)家( ARTIST ) 而言PAINTING 表中的數(shù)據(jù)是它的畫作, 而對于 PAINTING 表中的一條數(shù)據(jù)而言, ARTIST 表的對應(yīng)數(shù)據(jù)是這幅畫的作者點擊右邊的 "Add" 按鈕
"Source" 選擇 "ID" 列, "Target" 選擇 "ARTIST_ID" 列
-
關(guān)系信息應(yīng)如下圖所示:
[站外圖片上傳中...(image-9264b8-1524231454453)]
點擊 "Done" 以確認所做的修改并關(guān)閉對話框.
兩個關(guān)系已經(jīng)被創(chuàng)建: 從 ARTIST 到 PAINTING 的關(guān)系, 以及反向的關(guān)系. 不過你可能注意到有件事
忘記了: "paintings" 關(guān)系應(yīng)該是 to-many, 但是 "To Many" 復(fù)選框并沒有選中.
讓我們來改一下: 選中 "paintings" 關(guān)系的 "To Many" 復(fù)選框, 同時, 點擊 PAINTING DBEntity, 取消 "artist" 關(guān)系的 "To Many" 復(fù)選框以設(shè)置反向關(guān)系, 因為反向的 PAINTING 指向 ARTIST 的關(guān)系應(yīng)該是多對一(to-one).重復(fù)前面的步驟, 以建立從 PAINTING 到 GALLERY 的多對一關(guān)系, 讓我們將這對關(guān)系命名為 "gallery" 和 "paintings".
映射 Java 類
現(xiàn)在, 數(shù)據(jù)庫模式已經(jīng)映射完成, CayenneModeler 可以根據(jù)DbEntity中的所有內(nèi)容來創(chuàng)建Java類的映射(又稱作 "ObjEntity").
目前還不能直接通過一次點擊就完所有的 DataMap 映射, 因此我們將逐個表來做.
- 選擇 "ARTIST" DbEntity 并點擊實體工具欄或主工具欄上的 "Create ObjEntity" 按鈕 [站外圖片上傳中...(image-d774b3-1524231454453)], 一個名為 "Artist" 的 ObjEntity 即被創(chuàng)建出來, 同時 Java class 輸入框的值被設(shè)置為 "org.example.cayenne.Artist". 建模工具會將數(shù)據(jù)庫中的名稱轉(zhuǎn)換為 Java 風格的名稱 ( 例如: 如果你點擊 "Attributes" 標簽頁, 將看到 "DATA_OF_BIRTH" 列被轉(zhuǎn)換為 Java 類屬性 "dateOfBirth" ).
- 選擇 "GALLERY" DbEntity 并再次點擊 "Create ObjEntity" 按鈕, 你將看到一個 "Gallery" ObjEntity 被創(chuàng)建出來.
- 最后, 為 "PAINTING" 做同樣的操作.
現(xiàn)在, 你需要同步關(guān)系. 因為在還沒有相關(guān)聯(lián)的 "Painting" 實體 ( objEntity ) 之前, Artist 和 Gallery 實體就已經(jīng)被創(chuàng)建出來了. 因此, 他們之間的關(guān)系并未被自動設(shè)置.
- 點擊 "Artist" ObjEntity. 點擊工具欄上的 "Sync ObjEntity with DbEntity" 按鈕, 你會看到出現(xiàn)了 "paintings" 關(guān)系
- 對 "Gallery" 實體做同樣的操作.
除非你想要自定義 Java 類和屬性名 ( 你可以很容易地做到 ), 映射已經(jīng)完成了.
2.3. 創(chuàng)建Java類
這里我們將根據(jù)前面章節(jié)中創(chuàng)建的模型生成 Java 類.
CayenneModeler 同樣可用來生成數(shù)據(jù)庫模式, 因為在我們先前創(chuàng)建 DataNode 的時候指定了 "CreateIfNoSchemaStrategy", 因此我們將跳過創(chuàng)建數(shù)據(jù)庫模式的步驟. ( 譯注: 因為設(shè)置了CreateIfNoSchemaStrategy策略, 建模工具會自動創(chuàng)建相應(yīng)的數(shù)據(jù)庫模式 )
如果你有需要, 可通過 "Tools > Create Database Schema" 做到這一點 ( 生成數(shù)據(jù)庫模式 ).
創(chuàng)建 Java 類
- 選擇 "Tools > Generate Classes" 菜單
- "Type" 選擇 "Standard Persistent Objects" ( 如果沒有選中的話 )
- "Output Directory" 選擇你項目下的 "src/main/java" 文件夾 ( 這是與之前我們?yōu)?cayenne-*.xml 選擇的位置同等的位置 )
- 點擊 "Classess" 標簽, 選中 "Check All Classes" 復(fù)選框
- 點擊 "Generate"
現(xiàn)在回到 Eclipse, 右鍵點擊 "tutorial" 項目選擇 "Refresh" - 你應(yīng)該看到每個被映射的實體生成了兩個類.你可能也注意到, 有一堆紅色的波浪線在 Eclipse 中新出現(xiàn)的 Java 類旁邊.
譯注: 應(yīng)該是紅色的小叉叉吧~
這是因為我們的項目還沒有將 Cayenne 作為 Maven 的依賴包含進來.
讓我們來修復(fù)它, 在 pom.xml 文件的最下面插入 "cayenne-server" artifact.
最終的POM應(yīng)該像這樣:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example.cayenne</groupId>
<artifactId>tutorial</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.cayenne</groupId>
<artifactId>cayenne-server</artifactId>
<!-- 在這里指定你實際想使用的 Cayenne 版本. 譯注: 建議使用3.1.1這樣的RELEASE版本, 而不是SNAPSHOT版本 -->
<version>3.1.3-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
你的電腦必須連上Internet.
一旦你保存了 pom.xml, Eclipse 會自動下載 Cayenne 所需的 jar 文件, 并將其添加到項目構(gòu)建路徑 ( build path ).
最終, 所有的錯誤就會消失了.
[站外圖片上傳中...(image-de3854-1524231454453)]
現(xiàn)在, 讓我們來看一下實體類. 每個實體都有一個父類 ( 如: _Artist ) 和一個子類 ( 如: Artist ).
你不應(yīng)該修改名稱以"_" ( 下劃線 ) 開頭的父類, 因為他們將會在后續(xù)生成器運行的時候被覆蓋. 應(yīng)該把所有的自定義邏輯放在 "org.example.cayenne.persistent" 包的子類中(Artist), 這些子類不會被類生成器覆蓋.
類生成提示
通常你會先從 CayenneModeler 生成類, 但是在項目的后期階段, 通常通過Ant cgen task 或 Maven cgen mojo 自動生成代碼. 這三種方法均可, 但 Ant 和 Maven 方法可以確保您不會忘記在映射更改時重新生成類, 因為它們已集成到構(gòu)建周期中.
3. 學習Cayenne API
3.1. ObjectContext 入門
本節(jié)我們將寫一個簡單的 main 類來運行我們的應(yīng)用程序, 并對 Cayenne ObjectContext 作簡單的介紹.
創(chuàng)建 Main 類
- Eclipse中, 在 "org.example.cayenne" 包中創(chuàng)建一個新的類, 命名為 "Main"
- 創(chuàng)建一個標準的 "main" 方法, 以使其成為一個可運行的類:
package org.example.cayenne;
public class Main {
public static void main(String[] args) {
}
}
- 要訪問數(shù)據(jù)庫首先要做的是創(chuàng)建一個
ServerRuntime
對象 ( 這實質(zhì)上是對Cayenne的一個封裝 ), 并使用它獲得一個ObjectContext
的實例.
package org.example.cayenne;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.configuration.server.ServerRuntime;
public class Main {
public static void main(String[] args) {
ServerRuntime cayenneRuntime = new ServerRuntime(
"cayenne-project.xml");
ObjectContext context = cayenneRuntime.getContext();
}
}
在 Cayenne 中, ObjectContext
是一個獨立的 "Session", 它提供了處理數(shù)據(jù)所需的所有API.
ObjectContext 擁有執(zhí)行查詢和管理持久層對象的方法. 我們將在后續(xù)的章節(jié)中討論它們.
當應(yīng)用程序中的第一個 ObjectContext 被創(chuàng)建時, Cayenne 將加載 XML 映射文件, 并創(chuàng)建共享的訪問接口, 這將可被后續(xù)創(chuàng)建的其它 ObjectContext 重用.
運行應(yīng)用程序
讓我們來看一下運行程序時發(fā)生了什么.
但是在此之前, 我們需要添加其它的依賴 ( Apache Derby - 我們的嵌入式數(shù)據(jù)庫引擎 ) 到 pom.xml
.
下面這段XML代碼需要添加到 <dependencies>…?</dependencies>
部分, 在這里我們之前已經(jīng)添加過 Cayenne 所需的 jar:
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.8.1.2</version>
</dependency>
現(xiàn)在我們可以運行了.
在 Eclipse 中右鍵單擊 "Main" 類, 選擇"Run As > Java Application".
在控制臺你將看到類似如下的輸出. 這表示 Cayenne 已經(jīng)被啟動起來了:
INFO: Loading XML configuration resource from file:cayenne-project.xml
INFO: loading user name and password.
INFO: Created connection pool: jdbc:derby:memory:testdb;create=true
Driver class: org.apache.derby.jdbc.EmbeddedDriver
Min. connections in the pool: 1
Max. connections in the pool: 1
如何配置Cayenne的日志
按照日志一章中的介紹, 調(diào)整日志記錄輸出的詳盡程度.
譯注: 我沒找到所謂"日志一章"
3.2. 開始使用持久層對象
本節(jié)我們將學習關(guān)于持久層對象的知識, 如何定義它們, 如何創(chuàng)建并將其保存到數(shù)據(jù)庫.
檢視和定義持久層對象
在 Cayenne 中持久層類實現(xiàn)了一個數(shù)據(jù)對象 ( DataObject ) 接口.
如果你查看本教程此前生成的任何一個類 ( 如:org.example.cayenne.Artist ), 你會看到它繼承了一個名稱以下劃線開頭的類( 如: org.example.cayenne._Artist), 而這個類又繼承了 org.apache.cayenne.CayenneDataObject.
將每一個持久層類分解為一個用戶自定義子類 ( Xyz ) 和一個自動生成的父類 ( _Xyz ) 是一個很有用的技術(shù), 它將避免在刷新映射模型時覆蓋自定義的代碼.
讓我們來舉個例子, 添加一個工具方法到 Artist 類中, 用于設(shè)置出生日期. 此方法接收一個字符型的日期參數(shù). 即使后續(xù)模型發(fā)生變化, 這個方法亦將被保護 ( 避免被建模工具修改 ) :
public class Artist extends _Artist {
static final String DEFAULT_DATE_FORMAT = "yyyyMMdd";
/**
* Sets date of birth using a string in format yyyyMMdd.
*/
public void setDateOfBirthString(String yearMonthDay) {
if (yearMonthDay == null) {
setDateOfBirth(null);
} else {
Date date;
try {
date = new SimpleDateFormat(DEFAULT_DATE_FORMAT)
.parse(yearMonthDay);
} catch (ParseException e) {
throw new IllegalArgumentException(
"A date argument must be in format '"
+ DEFAULT_DATE_FORMAT + "': " + yearMonthDay);
}
setDateOfBirth(date);
}
}
}
創(chuàng)建新對象
現(xiàn)在我們將創(chuàng)建一組對象, 并將其保存到數(shù)據(jù)庫.
使用 ObjectContext
的"newObject"方法可創(chuàng)建并注冊一個對象.
對象必須被注冊到 DataContext
才能被持久化, 也才能被允許設(shè)置與其它對象的關(guān)系.
添加如下代碼到 Main 類的 "main" 方法中:
Artist picasso = context.newObject(Artist.class);
picasso.setName("Pablo Picasso");
picasso.setDateOfBirthString("18811025");
注意, 此時對象 picasso 僅被存儲于內(nèi)存中, 還未被保存到數(shù)據(jù)庫.
讓我們繼續(xù)添加一個名為 Metropolitan Museum 的 "Gallery" 對象, 和一些畢加索的畫作( Paintings ).
Gallery metropolitan = context.newObject(Gallery.class);
metropolitan.setName("Metropolitan Museum of Art");
Painting girl = context.newObject(Painting.class);
girl.setName("Girl Reading at a Table");
Painting stein = context.newObject(Painting.class);
stein.setName("Gertrude Stein");
現(xiàn)在我們可以把這些對象關(guān)聯(lián)起來, 建立關(guān)系.
注意, 在下面的每一個例子里, 雙向的關(guān)系均被自動建立起來. ( 例如: picasso.addToPaintings(girl) 完全等效于 girl.setToArtist(picasso) ).
picasso.addToPaintings(girl);
picasso.addToPaintings(stein);
girl.setGallery(metropolitan);
stein.setGallery(metropolitan);
現(xiàn)在, 讓我們使用一個方法來同時保存所有的5個對象:
context.commitChanges();
現(xiàn)在, 你可以使用前面章節(jié)中所述的方法的來再次運行程序.
新的輸出將顯示一些實際的數(shù)據(jù)庫操作:
...
org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader load
INFO: Loading XML configuration resource from file:cayenne-project.xml
...
INFO: Opening connection: jdbc:derby:memory:testdb;create=true
Login: null
Password: *******
INFO: +++ Connecting: SUCCESS.
INFO: Detected and installed adapter: org.apache.cayenne.dba.derby.DerbyAdapter
INFO: --- transaction started.
INFO: No schema detected, will create mapped tables
INFO: CREATE TABLE GALLERY (ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE ARTIST (DATE_OF_BIRTH DATE, ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE PAINTING (ARTIST_ID INTEGER, GALLERY_ID INTEGER, ID INTEGER NOT NULL,
NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (ARTIST_ID) REFERENCES ARTIST (ID)
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (GALLERY_ID) REFERENCES GALLERY (ID)
INFO: CREATE TABLE AUTO_PK_SUPPORT (
TABLE_NAME CHAR(100) NOT NULL, NEXT_ID BIGINT NOT NULL, PRIMARY KEY(TABLE_NAME))
INFO: DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('ARTIST', 'GALLERY', 'PAINTING')
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('ARTIST', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('GALLERY', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PAINTING', 200)
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'ARTIST']
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'GALLERY']
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'PAINTING']
INFO: INSERT INTO GALLERY (ID, NAME) VALUES (?, ?)
INFO: [batch bind: 1->ID:200, 2->NAME:'Metropolitan Museum of Art']
INFO: === updated 1 row.
INFO: INSERT INTO ARTIST (DATE_OF_BIRTH, ID, NAME) VALUES (?, ?, ?)
INFO: [batch bind: 1->DATE_OF_BIRTH:'1881-10-25 00:00:00.0', 2->ID:200, 3->NAME:'Pablo Picasso']
INFO: === updated 1 row.
INFO: INSERT INTO PAINTING (ARTIST_ID, GALLERY_ID, ID, NAME) VALUES (?, ?, ?, ?)
INFO: [batch bind: 1->ARTIST_ID:200, 2->GALLERY_ID:200, 3->ID:200, 4->NAME:'Gertrude Stein']
INFO: [batch bind: 1->ARTIST_ID:200, 2->GALLERY_ID:200, 3->ID:201, 4->NAME:'Girl Reading at a Table']
INFO: === updated 2 rows.
INFO: +++ transaction committed.
Cayenne 創(chuàng)建了必要的表 ( 記住, 我們使用了 "CreateIfNoSchemaStrategy").
然后它運行一些插入, 即時生成了主鍵.
就這么幾行代碼就搞成這樣還不賴.
3.3. 檢索對象
本節(jié)演示如何使用 ObjectSelect
來從數(shù)據(jù)庫中檢索 ( 查詢 ) 對象.
ObjectSelect 介紹
前面已經(jīng)展示了如何持久化新的對象.
Cayenne 的 query 被用來訪問已經(jīng)保存的對象.
用于檢索對象的主要查詢類型是 ObjectSelect
. 它可以直接在 CayenneModeler 中進行映射, 也可以通過 API 創(chuàng)建. 本節(jié)我們將使用后一種方法.
雖然我們還沒有太多的數(shù)據(jù)在數(shù)據(jù)庫中, 但是我們?nèi)钥梢匝菔救缦碌闹饕椒?
- 檢索所有的畫作 ( 代碼 及 產(chǎn)生的日志輸出 ):
SelectQuery select1 = new SelectQuery(Painting.class);
List paintings1 = context.performQuery(select1);
INFO: SELECT t0.GALLERY_ID, t0.ARTIST_ID, t0.NAME, t0.ID FROM PAINTING t0
INFO: === returned 2 rows. - took 18 ms.
- 檢索以 "gi" 開頭的畫作, 忽略大小寫:
Expression qualifier2 = ExpressionFactory.likeIgnoreCaseExp(
Painting.NAME_PROPERTY,
"gi%");
SelectQuery select2 = new SelectQuery(Painting.class, qualifier2);
List paintings2 = context.performQuery(select2);
INFO: SELECT t0.GALLERY_ID, t0.NAME, t0.ARTIST_ID, t0.ID FROM PAINTING t0 WHERE UPPER(t0.NAME) LIKE UPPER(?)
[bind: 1->NAME:'gi%'] - prepared in 6 ms.
INFO: === returned 1 row. - took 18 ms.
- 檢索所有100年前出生的藝術(shù)家的畫作 ( 演示使用 Expression.fromString(..) 而不是 ExpressionFactory ):
Calendar c = new GregorianCalendar();
c.set(c.get(Calendar.YEAR) - 100, 0, 1, 0, 0, 0);
Expression qualifier3 = Expression.fromString("artist.dateOfBirth < $date");
qualifier3 = qualifier3.expWithParameters(Collections.singletonMap("date", c.getTime()));
SelectQuery select3 = new SelectQuery(Painting.class, qualifier3);
List paintings3 = context.performQuery(select3);
INFO: SELECT t0.GALLERY_ID, t0.NAME, t0.ARTIST_ID, t0.ID FROM PAINTING t0 JOIN ARTIST t1 ON (t0.ARTIST_ID = t1.ID)
WHERE t1.DATE_OF_BIRTH < ? [bind: 1->DATE_OF_BIRTH:'1911-01-01 00:00:00.493'] - prepared in 7 ms.
INFO: === returned 2 rows. - took 25 ms.
3.4. 刪除對象
本節(jié)解釋如何建立關(guān)系的刪除約束模型, 如何刪除單個對象和一組對象.
同時, 也將演示執(zhí)行查詢時 Cayenne 類的使用.
設(shè)置刪除約束
在我們討論刪除對象的 API 前, 讓我們回到 CayenneModeler, 進行一些刪除約束的設(shè)置.
這樣做是可選的 ( 不是必須的 ) , 但它將使得我們可以以簡單的方式正確處理與被刪除對象相關(guān)聯(lián)的其它對象.
在建模工具中轉(zhuǎn)到 "Artist" ObjEntity 的 "Relationships" 標簽頁, 為 "paintings" 關(guān)系選擇 "Cascade" ( 級聯(lián) ) 刪除約束:
[站外圖片上傳中...(image-a4dd47-1524231454453)]
為其它關(guān)系重復(fù)上述步驟:
- 為 Gallery 設(shè)置 "paintings" 關(guān)系為 "Nullify", 因為可以存在一副畫作未在任何畫廊展出的情況.
- 為 Painting 設(shè)置其兩個關(guān)系的刪除約束均為 "Nullify".
現(xiàn)在, 保存映射.
刪除對象
雖然可以通過SQL刪除對象. 但在Cayenne( 或一般的ORM )中更常用的方法是首先獲取對象, 然后通過 context 刪除它.
讓我們使用 Cayenne 的工具類找到一個藝術(shù)家:
Expression qualifier = ExpressionFactory.matchExp(Artist.NAME_PROPERTY, "Pablo Picasso");
SelectQuery select = new SelectQuery(Artist.class, qualifier);
Artist picasso = (Artist) Cayenne.objectForQuery(context, select);
現(xiàn)在, 讓我們刪除這個藝術(shù)家:
if (picasso != null) {
context.deleteObject(picasso);
context.commitChanges();
}
因為我們?yōu)?Artist.paintings 關(guān)系設(shè)置了 "Cascade" 刪除約束, Cayenne會自動刪除這個藝術(shù)家所有的畫作.
因此, 當我們運行這個程序時, 你將會看到如下輸出:
INFO: SELECT t0.DATE_OF_BIRTH, t0.NAME, t0.ID FROM ARTIST t0
WHERE t0.NAME = ? [bind: 1->NAME:'Pablo Picasso'] - prepared in 6 ms.
INFO: === returned 1 row. - took 18 ms.
INFO: +++ transaction committed.
INFO: --- transaction started.
INFO: DELETE FROM PAINTING WHERE ID = ?
INFO: [batch bind: 1->ID:200]
INFO: [batch bind: 1->ID:201]
INFO: === updated 2 rows.
INFO: DELETE FROM ARTIST WHERE ID = ?
INFO: [batch bind: 1->ID:200]
INFO: === updated 1 row.
INFO: +++ transaction committed.
4. 轉(zhuǎn)換為Web應(yīng)用程序
本章將展示 Cayenne 如何在Web應(yīng)用程序中工作
4.1. 將 tutorial 項目轉(zhuǎn)換為 Web應(yīng)用程序
Web應(yīng)用程序教程的 Web 部分是在 JSP 中完成的, 而 JSP 是 Java Web 技術(shù)中最常見的實現(xiàn)方法.
本教程在 UI 方面盡可能地簡單, 主要專注于 Cayenne 集成, 而不是界面.
一個典型的 Cayenne Web 應(yīng)用程序像下面這樣工作:
- 在應(yīng)用程序上下文啟動時, 使用一個特定的 Servlet 過濾器加載 Cayenne 的配置
- 用戶請求被過濾器攔截, 并將 DataContext 綁定到請求線程, 因此應(yīng)用程序可以從任何地方輕松訪問它.
- 同一個 DataContext 實例在單個用戶會話 ( Session ) 中被重用; 不同的會話使用不同的 DataContexts ( 以及不同的對象集). 根據(jù)應(yīng)用的具體情況, 上下文可以有不同的范圍. 本教程中我們將使用會話范圍的上下文 ( Context ).
譯注: 每一個 DataContext 是一個獨立的數(shù)據(jù)上下文空間, 其中包含了從數(shù)據(jù)庫中調(diào)入的 ( 和新創(chuàng)建尚未持久化的 ) 數(shù)據(jù)對象集合. 在實際 Web 項目中如果像這樣將 DataContext 綁定到用戶會話級, 在多用戶并發(fā)時可能導(dǎo)致內(nèi)存中有很多冗余的數(shù)據(jù)對象. 也有可能出現(xiàn)多個用戶分別從數(shù)據(jù)庫檢索同一數(shù)據(jù)后, 其中一個用戶修改了數(shù)據(jù), 導(dǎo)致其它用戶持有的數(shù)據(jù)對象是 "臟數(shù)據(jù)". 所以實際 Web 項目中本例所述的做法有待斟酌...
讓我們將我們此前創(chuàng)建的 tutorial 項目轉(zhuǎn)換為一個 Web 應(yīng)用程序:
- 在Eclipse中的 "tutorial" 項目下創(chuàng)建一個新的文件夾 "src/main/webapp/WEB-INF".
- 在
WEB-INF
下創(chuàng)建一個新文件web.xml
( 一個標準的Web應(yīng)用程序描述文件 ):
web.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Cayenne Tutorial</display-name>
<!-- This filter bootstraps ServerRuntime and then provides each request thread
with a session-bound DataContext. Note that the name of the filter is important,
as it points it to the right named configuration file.
-->
<filter>
<filter-name>cayenne-project</filter-name>
<filter-class>org.apache.cayenne.configuration.web.CayenneFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cayenne-project</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
- 創(chuàng)建一個藝術(shù)家瀏覽頁面
src/main/webapp/index.jsp
, 包含如下內(nèi)容:
webapp/index.jsp
<%@ page language="java" contentType="text/html" %>
<%@ page import="org.example.cayenne.persistent.*" %>
<%@ page import="org.apache.cayenne.*" %>
<%@ page import="org.apache.cayenne.query.*" %>
<%@ page import="org.apache.cayenne.exp.*" %>
<%@ page import="java.util.*" %>
<%
SelectQuery query = new SelectQuery(Artist.class);
query.addOrdering(Artist.NAME_PROPERTY, SortOrder.ASCENDING);
ObjectContext context = BaseContext.getThreadObjectContext();
List<Artist> artists = context.performQuery(query);
%>
<html>
<head>
<title>Main</title>
</head>
<body>
<h2>Artists:</h2>
<% if(artists.isEmpty()) {%>
<p>No artists found</p>
<% } else {
for(Artist a : artists) {
%>
<p><a href="detail.jsp?id=<%=Cayenne.intPKForObject(a)%>"> <%=a.getName()%> </a></p>
<%
}
} %>
<hr>
<p><a href="detail.jsp">Create new artist...</a></p>
</body>
</html>
- 創(chuàng)建一個藝術(shù)家編輯頁面
src/main/webapp/detail.jsp
, 包含如下內(nèi)容:
webapp/detail.jsp
<%@ page language="java" contentType="text/html" %>
<%@ page import="org.example.cayenne.persistent.*" %>
<%@ page import="org.apache.cayenne.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.text.*" %>
<%
ObjectContext context = BaseContext.getThreadObjectContext();
String id = request.getParameter("id");
// find artist for id
Artist artist = null;
if(id != null && id.trim().length() > 0) {
artist = Cayenne.objectForPK(context, Artist.class, Integer.parseInt(id));
}
if("POST".equals(request.getMethod())) {
// if no id is saved in the hidden field, we are dealing with
// create new artist request
if(artist == null) {
artist = context.newObject(Artist.class);
}
// note that in a real application we would so dome validation ...
// here we just hope the input is correct
artist.setName(request.getParameter("name"));
artist.setDateOfBirthString(request.getParameter("dateOfBirth"));
context.commitChanges();
response.sendRedirect("index.jsp");
}
if(artist == null) {
// create transient artist for the form response rendering
artist = new Artist();
}
String name = artist.getName() == null ? "" : artist.getName();
String dob = artist.getDateOfBirth() == null
? "" : new SimpleDateFormat("yyyyMMdd").format(artist.getDateOfBirth());
%>
<html>
<head>
<title>Artist Details</title>
</head>
<body>
<h2>Artists Details</h2>
<form name="EditArtist" action="detail.jsp" method="POST">
<input type="hidden" name="id" value="<%= id != null ? id : "" %>" />
<table border="0">
<tr>
<td>Name:</td>
<td><input type="text" name="name" value="<%= name %>"/></td>
</tr>
<tr>
<td>Date of Birth (yyyyMMdd):</td>
<td><input type="text" name="dateOfBirth" value="<%= dob %>"/></td>
</tr>
<tr>
<td></td>
<td align="right"><input type="submit" value="Save" /></td>
</tr>
</table>
</form>
</body>
</html>
運行Web應(yīng)用程序
為了運行這個Web應(yīng)用程序, 我們將使用 "maven-jetty-plugin".
譯注: Jetty 是一個 Web 應(yīng)用程序容器, 作用類似 Tomcat.
為了激活它, 讓我們添加如下的代碼到 "pom.xml" 中, 跟在 "dependencies" 部分的后面, 保存POM.
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.3.14.v20161028</version>
</plugin>
</plugins>
</build>
- 打開 "Run > Run Configurations…?" 菜單, 選擇 "Maven Build", 點擊右鍵并選擇 "New"
- 確定你填寫了 "Name", "Base directory" 和 "Goals", 如下圖:
[站外圖片上傳中...(image-83c748-1524231454453)]
- 依次點擊 "Apply" 和 "Run".
首次運行時可能會花費幾分鐘下載Jetty插件所有的依賴,
但是最終你將看到如下的日志:
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building tutorial 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] Configuring Jetty for project: tutorial
[INFO] Webapp source directory = /.../tutorial/src/main/webapp
[INFO] Reload Mechanic: automatic
[INFO] Classes = /.../tutorial/target/classes
[INFO] Context path = /tutorial
[INFO] Tmp directory = determined at runtime
[INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] web.xml file = /.../tutorial/src/main/webapp/WEB-INF/web.xml
[INFO] Webapp directory = /.../tutorial/src/main/webapp
[INFO] Starting jetty 6.1.22 ...
INFO::jetty-6.1.22
INFO::No Transaction manager found - if your webapp requires one, please configure one.
INFO::Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
至此, Jetty 容器已經(jīng)啟動了.
-
現(xiàn)在, 在瀏覽器中打開網(wǎng)址 http://localhost:8080/tutorial/.
你應(yīng)該在瀏覽器中看到 "No artists found message", 同時在 Eclipse 的控制臺中可看到如下輸出:
INFO: Loading XML configuration resource from file:/.../tutorial/target/classes/cayenne-project.xml
INFO: loading user name and password.
INFO: Created connection pool: jdbc:derby:memory:testdb;create=true
Driver class: org.apache.derby.jdbc.EmbeddedDriver
Min. connections in the pool: 1
Max. connections in the pool: 1
INFO: Opening connection: jdbc:derby:memory:testdb;create=true
Login: null
Password: *******
INFO: +++ Connecting: SUCCESS.
INFO: Detected and installed adapter: org.apache.cayenne.dba.derby.DerbyAdapter
INFO: --- transaction started.
INFO: No schema detected, will create mapped tables
INFO: CREATE TABLE GALLERY (ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE ARTIST (DATE_OF_BIRTH DATE, ID INTEGER NOT NULL, NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: CREATE TABLE PAINTING (ARTIST_ID INTEGER, GALLERY_ID INTEGER, ID INTEGER NOT NULL,
NAME VARCHAR (200), PRIMARY KEY (ID))
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (ARTIST_ID) REFERENCES ARTIST (ID)
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (GALLERY_ID) REFERENCES GALLERY (ID)
INFO: CREATE TABLE AUTO_PK_SUPPORT (
TABLE_NAME CHAR(100) NOT NULL, NEXT_ID BIGINT NOT NULL, PRIMARY KEY(TABLE_NAME))
INFO: DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('ARTIST', 'GALLERY', 'PAINTING')
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('ARTIST', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('GALLERY', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PAINTING', 200)
INFO: SELECT t0.DATE_OF_BIRTH, t0.NAME, t0.ID FROM ARTIST t0 ORDER BY t0.NAME - prepared in 43 ms.
INFO: === returned 0 rows. - took 56 ms.
INFO: +++ transaction committed.
- 你可以點擊 "Create new artist" 鏈接去新建藝術(shù)家. 對于已存在的藝術(shù)家的可以通過點擊他的名字來進行編輯.
[站外圖片上傳中...(image-f6fcc3-1524231454453)]
你已完成了本教程!