play框架07--域模型
7.1屬性模擬
檢視Play提供的示例應用,模型類裡面會頻繁地使用宣告為public的變數。即使是經驗尚淺的Java開發者,也懂得慎用public型別的變數。在Java開發中(當然還有其他的面嚮物件語言),實踐經驗是這樣告訴我們的:將所有的成員變數宣告為私有,只提供獲取與修改的方法。這樣做的目的在於增強程式的封裝性,而“封裝”在面向物件設計中恰恰是非常關鍵的概念。
Java沒有真正的內建屬性定義機制,而是使用Java Bean來進行約束:Java物件的屬性通過一對getXxx/setXxx的方法來修改,如果物件是隻讀的那麼只需要提供getXxx方法。在過去的開發中我們一直這樣做,但是編碼過程就顯得有些乏味了。每個屬性必須宣告為private,同時還有相應的getXxx/setXxx方法,而且大多數情況下,getXxx和setXxx方法的實現都是類似的。
private String name;
public String getName() {
return name;
}
public void setName(String value) {
name = value;
}
Play框架的模型部分會自動生成getXxx/setXxx方法,保持程式碼的簡潔。也就是說,在Play中開發者可以直接把屬性變數宣告為public,執行時Play會自動生成相應的getXxx/setXxx方法(在這裡我們將宣告為public的欄位都視為屬性)。
public class Product {
public String name;
public Integer price;
}
上述程式碼被框架載入後就會轉換成如下形式:
public class Product {
public String name;
public Integer price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
因為變數被宣告為public,可以使用如下方式對屬性進行操作:
product.name = "My product";程式在載入時會自動地轉換成:
product.price = 58;
product.setName("My product");
product.setPrice(58);
因為這些getXxx/setXxx方法是在執行時動態生成的,所以不能直接呼叫。如果在編碼階段使用他們,編譯器會因為找不到該方法而報錯誤。
當然也可以自己定義相應的getXxx/setXxx方法,Play會優先選擇手動編寫的方法。如果需要保護Product類的price屬性就可以定義setPrice()方法:
public class Product {如果為Product類的price屬性賦負值就會丟擲異常:
public String name;
public Integer price;
public void setPrice(Integer price) {
if (price < 0) {
throw new IllegalArgumentException("Price can’t be negative!");
}
this.price = price;
}
}
product.price = -10: // Oops! IllegalArgumentExceptionPlay總會優先使用已經定義的getXxx/setXxx方法:
@Entity
public class Data extends Model {
@Required
public String value;
public Integer anotherValue;
public Integer getAnotherValue() {
if(anotherValue == null) {
return 0;
}
return anotherValue;
}
public void setAnotherValue(Integer value) {
if(value == null) {
this.anotherValue = null;
} else {
this.anotherValue = value * 2;
}
}
public String toString() {
return value + " - " + anotherValue;
}
}
@Entity註解的作用是通知Play自動開啟JPA實體管理器,@Required是對該屬性的約束。該類繼承於play.db.jpa.Model,Model提供了非常簡單的物件處理方式,在後面章節會做詳細介紹。
針對以上例子可以進行如下測試斷言:
Data data = new Data();
data.anotherValue = null;
assert data.anotherValue == 0;
data.anotherValue = 4
assert data.anotherValue == 8;
以上的斷言都會執行通過,而且因為這種改進的類遵從JavaBean規範,可以滿足開發中的更復雜需求。
7.2、資料庫配置
通常情況下,開發者需要將模型物件持久化。最常用的方法是把這些資料儲存到資料庫中。
在Play應用的開發過程中,開發者可以迅速配置嵌入式記憶體資料庫或者直接將資料儲存到檔案系統中。開啟記憶體資料庫H2,只需要在conf/application.conf檔案中進行如下配置:
db=mem
H2是開放原始碼的Java資料庫,其具有標準的SQL語法和Java介面,可以自由使用和分發,且非常簡潔和快速。將資料儲存在記憶體中相比從磁碟上訪問能夠極大地提高應用的效能,但由於記憶體容量的限制,記憶體資料庫適用於開發階段,或者原型示例開發。
如果需要將資料儲存在檔案系統中,則使用如下配置:
db=fs如果需要連線到MySQL伺服器,則使用如下配置:
db=mysql:user:[email protected]_name
Play框架集成了H2資料庫和MySQL資料庫的驅動程式,存放在$PLAY_HOME/framework/lib/目錄下。如果需要使用PostgreSQL,Oracle或者其他資料庫,需要在該目錄(或者應用程式的lib/目錄)下新增相應的資料庫驅動。
Play可以連線任何JDBC相容的資料庫,只需要將相應的驅動類庫新增到/lib目錄中,並在conf/application.conf檔案中定義JDBC配置:
db.url=jdbc:mysql://localhost/test還可以在conf/application.conf檔案中用配置選項指定JPA方言:
db.driver=com.mysql.jdbc.Driver
db.user=root
db.pass=123456
jpa.dialect=<dialect>
由於不同的資料庫產品支援不同的ANSI SQL標準,所以Hibernate必須要使用“方言”才能與各種資料庫成功的進行通訊。在Play中,大多數情況下會自動根據配置資訊識別特定資料庫方言,但是存在某些資料庫,Play無法判斷其使用的方言。這時就需要開發者顯式地在Play配置檔案中指定。
除了使用Hibernate外,在編碼時還可以直接從play.db.DB中獲得java.sql.Connection,然後使用標準SQL語句來執行資料庫操作。
Connection conn = DB.getConnection();
conn.createStatement().execute("select * from products");
7.3、資料持久化
Play的持久層框架採用的是Hibernate,使用Hibernate(通過JPA)自動地將Java物件持久化到資料庫。當在任意的實體類上增加@javax.persistence.Entity註解後,Play會自動為其開啟JPA實體管理器。
@Entity
public class Product {
public String name;
public Integer price;
}
Play應用開發者一開始可能經常會犯的錯誤是使用Hibernate的@Entity註解來取代JPA。這裡請讀者注意,Play是直接呼叫JPA的API來使用Hibernate。
也可以直接從play.db.jpa.JPA物件中得到實體管理器,通過實體管理器可以將Model持久化到資料庫或者執行HQL語句,例如:
EntityManager em = JPA.em();
em.persist(product);
em.createQuery("from Product where price > 50").getResultList();
Play為JPA的使用提供了非常好的支援,只需要繼承Play提供的play.db.jpa.Model類:
@Entity接著就可以執行Product例項中CRUD操作進行物件持久化:
public class Product extends Model {
public String name;
public Integer price;
}
Product.find("price > ?", 50).fetch();補充:ActiveRecord模式
Product product = Product.findById(2L);
product.save();
product.delete();
ActiveRecord也屬於ORM層,由Rails最早提出,遵循標準的ORM模型:表對映到記錄,記錄對映到物件,欄位對映到物件屬性。配合遵循的命名和配置慣例,能夠很大程度的快速實現模型的操作,而且簡潔易懂。Play也提倡使用ActiveRecord模式進行快速開發,其主要思想是:
- 每一個數據庫表對應建立一個類,類的每一個物件例項對應於資料庫中表的一行記錄;通常表的每個欄位在類中都有相應的Field;
- ActiveRecord同時負責把自己持久化,在ActiveRecord中封裝了對資料庫的訪問,即CURD;
- ActiveRecord是一種領域模型(Domain Model),封裝了部分業務邏輯。
7.4、無狀態模型
Play被設計成為“無共享”的架構,目的就是為了保持應用的完全無狀態化。這樣做的好處在於可以讓一個應用同一時刻在多個伺服器節點上執行。
Play為了保持模型無狀態化,需要避免一些常見的陷阱,最重要的就是不要因為多請求而將物件儲存到Java堆中。Play應用中多請求之間儲存資料有以下幾種解決方案:
1.如果資料很小而且非常簡單,那麼可以將其儲存在Session或者Flash作用域,但是這些作用域最大隻允許存放4K的內容,並且儲存的資料只能為字串型別。
2.將資料儲存到持久化儲存中(資料庫或者檔案系統)。比如使用者建立了需要跨越多個請求的物件,就可以按照以下步驟對其進行操作:
- 在第一次請求時初始化物件並將它儲存到資料庫中。
- 將建立的物件的id儲存在Flash作用域中。
- 在以後不停的請求鏈執行過程中,使用id從資料庫中獲取物件,更新並重新儲存它。
3.將資料儲存在瞬時儲存中(比如Cache):
- 在第一次請求時初始化物件並將它儲存在快取中。
- 將建立的物件的id儲存在Flash作用域中。
- 在請求鏈的執行過程中,從Cache裡獲取物件(通過儲存在Flash作用域中的物件id),更新後並將它再次儲存回Cache。
- 當請求鏈結束後,將物件進行持久化操作(資料庫或者檔案系統)。
根據具體應用的需求,第三種解決方案使用快取可以是一種非常好的選擇,也是Java Servlet Session的良好的替代方案。但快取並不是可靠的資料儲存方式,因此如果選擇將物件儲存到快取中,就必須確保能夠將它重新讀取回來。