模型是play應用程序中核心。是應用程序操作的信息的特定領域呈現。
Martin Fowler將之定義爲:
模型層主要負責表現商業內容、商業狀態和商業規則的信息。在這裏主要進行商業狀態控制和作用,相應技術細節則委託給基礎設施。這個層是商業軟件的最重要部分。
通用的java反映模式用許多簡單的java Bean來映射模型以保存數據,應用程序邏輯被放入“service”層,用於操作模型對象。
Martin fowler把這種反映模式命名爲貧血對象模型
貧血對象模型的基本特徵就是外表看起來就是一個真實的事物,但在模型裏幾乎沒有行爲,只有getter和setter,不能在模型對象裏放入邏輯。模型的行爲通過許多包括有域邏輯的service對象來實現。
這樣的模型是和OO相反的,OO對象既有數據也有對象的方法。
屬性模仿
在play裏,類的變量都是public的。這引起java開發界的一些質疑。在java的標準教程裏,爲了對數據進行封閉,要求屬性都是private的。這導致了一些批評。
java並沒有真正的內建屬性定義系統。只是一個Java Bean的約定:在java對象裏的屬性都要有getter和setter方法,如果屬性是隻讀的,那麼只能有getter。
雖然系統可以很好地工作,但編碼十分乏味。每個屬性都必須聲明爲私有變量併爲此書寫getter和setter。因此,許多時候,getter和setter實現都是相同的:
private String name;
public String getName() {
return name;
}
public void setName(String value) {
name = value;
}
在play裏,play會爲模型自動生成這些內容,以保證代碼清晰。事實上,所有public變量都是實例屬性。在play里約定爲:類的任何public,non-static,no-final域都將以屬性對待。
比如:
public class Product {
public Stringname;
public Integerprice;
}
類在加載時,就變成了如下內容:
public class Product {
public Stringname;
public Integerprice;
public StringgetName() {
returnname;
}
public voidsetName(String name) {
this.name =name;
}
public IntegergetPrice() {
returnprice;
}
public voidsetPrice(Integer price) {
this.price= price;
}
}
要訪問一個屬性裏,只需要以下代碼:
product.name = "My product";
product.price = 58;
在加載時會自動翻譯爲:
product.setName("My product");
product.setPrice(58);
注意!
在自動生成方式下,不能直接作用getter和setter方法來訪問屬性。這些方法僅存在於運行時狀態,因此,如果在編寫代碼時調用這些方法,將導致不能找到方法的編譯錯誤。
當然也可自行定義getter和setter方法。如果自行定義了這兩個方法,play會自動使用這兩個手工編寫的方法。
爲了保護Product類的price屬性值,可以使用以下方法:
public class Product {
public Stringname;
public Integerprice;
public voidsetPrice(Integer price) {
if (price< 0) {
thrownew IllegalArgumentException("Price can’t be negative!");
}
this.price= price;
}
}
然後試着給price賦值一個負數時,就會拋出參數異常:
product.price = -10: // Oops! IllegalArgumentException
Play總是會使用已經手工定義好的getter或setter,如下:
@Entity
public class Data extends Model {
@Required
public Stringvalue;
public IntegeranotherValue;
public IntegergetAnotherValue() {
if(anotherValue == null) {
return0;
}
returnanotherValue;
}
public voidsetAnotherValue(Integer value) {
if(value ==null) {
this.anotherValue = null;
} else {
this.anotherValue = value * 2;
}
}
public StringtoString() {
return value + " - " +anotherValue;
}
}
在另外一個類裏對上面的類進行斷言:
Data data = new Data();
data.anotherValue = null;
assert data.anotherValue == 0;
data.anotherValue = 4
assert data.anotherValue == 8;
正常工作,這是因爲增加了getter和setter的類遵循JavaBean約定。
設置數據庫來持久化模型對象
很多時候都需要把模型對象數據永久保存,最自然的方式就是把數據存入數據庫。
在開發期間,可以直接使用內建的數據庫來臨時保存數據到內存或一個子目錄裏。
play發佈包裏包含了H2和Mysql的JDBC驅動($PLAY_HOME/framework/lib/)。如果打算使用PostgreSQL 或Oracle數據庫,那麼就需要把對應的jdbc驅動放入庫目錄裏,或放入應用程序的lib目錄。
連接到任何JDBC規範的數據,只需要設置jdbc的db.url,db.driver, db.user 和 db.pass屬性:
db.url=jdbc:mysql://localhost/test
db.driver=com.mysql.jdbc.Driver
db.user=root
db.pass=
使用jpa.dialect屬性可以爲jpa定義方言。
在代碼裏,就可以從play.db.DB獲取一個java.sql.Connection。
Connection conn = DB.getConnection();
conn.createStatement().execute("select * fromproducts");
用hibernate持久化對象模型
使用hibernate來自動持久化java對象到數據庫裏。
在任何java對象增加 @javax.persistence.Entity註釋就可以定義一個jpa實體。play會自動啓動一個jpa實體管理器。
@Entity
public class Product {
public Stringname;
public Integerprice;
}
注意!
一個經常發生的錯誤是使用hibernate的@Entity註釋來代替JPA註釋。請記住,play是通過hibernate來使用的jpa的api。也就是說要引入:javax.persistence.Entity
包,而不是hibernate的包。
然後就可以從play.db.jpa.JPA獲取一個EntityManager:
EntityManager em = JPA.em();
em.persist(product);
em.createQuery("from Product where price >50").getResultList();
play提供了一個非常漂亮的支持類來處理jpa,只需要實體類繼承play.db.jpa.Model即可。
@Entity
public class Product extends Model {
public Stringname;
public Integerprice;
}
之後使用Product實例的簡單方法就可以操作Product對象:
Product.find("price > ?", 50).fetch();
Product product = Product.findById(2L);
product.save();
product.delete();
保持模型stateless
play被設計成“什麼都不共享”的機制。這個機制用於保持應用是完全無狀態的。這樣就允許程序可以同時運行於多個服務器節點。
Play 框架的設計架構就是無狀態的。它沒有提供服務器端的機制用來維護跨多個請求的數據。如果確實需要保存這樣的數據的話,可以考慮下面幾種方案:
- 如果數據很少很簡單,可以存儲在flash或session使用域裏。但這些域只能存儲小於4kb的數據,並且只能是字符串類型的數據。
- 使用數據庫,比如需要創建一個橫跨多個請求的wizard對象:
- 在第一次請求時初始化並持久化對象到數據庫中。
- 把新創建的對象的ID存儲到flash或session域中。
- 在接下來的請求中,使用對象id找回數據、更新數據、再次存儲等。
- 暫時存儲到Cache中,比如需要創建一個橫跨多個請求的wizard對象:
- 在第一次請求時初始化並持久化對象到Cache中。
- 把新創建的對象的key存儲到flash或session域中。
- 在接下來的請求中,使用正確的key找回數據、更新數據、再次存儲等。
- 在結束最後一次請求後,把對象永久存儲到數據庫中。
Cache不是一個可靠的存儲位置,在系統未出現故障時應該可以正確操作數據。但Cache是比Java Servlet session更好的選擇。