模型是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更好的选择。