Play 框架手册(6) – 域对象模型


模型是 play 应用程序中核心。是应用程序操作的信息的特定领域呈现。

Martin Fowler 将之定义为:

模型层主要负责表现商业内容、商业状态和商业规则的信息。在这里主要进行商业状态控制和作用,相应技术细节则委托给基础设施。这个层是商业软件的最重 要部分。

通用的 java 反映模式用许多简单的 java Bean 来映射模型以保存数据,应用程 序逻辑被放入“service”层,用于操作模型对象。

Martin fowler 把这种反映模式命名为贫血对象模型 Anemic object model: 

贫血对象模型的基本特征就是外表看起来就是一个真实的事物, 但在模型里几乎没有行为,只有 getter 和 setter,不能在模型对象里放入逻辑。模型的行为通 过许多包括有域逻辑的 service 对象来实现。

这样的模型是和 OO 相反的,OO 对象既有数据也有对象的方法。

6.1. 属性模仿

在 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 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;
    }
}

要访问一个属性里,只需要以下代码:

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 String name;
    public Integer price;

    public void setPrice(Integer price) {
        if (price < 0) {
            throw new 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 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;
   }

}

在另外一个类里对上面的类进行断言:

Data data = new Data();
data.anotherValue = null;
assert data.anotherValue == 0;
data.anotherValue = 4
assert data.anotherValue == 8;

正常工作,这是因为增加了 getter 和 setter 的类遵循 Java Bean 约定。

6.2. 设置数据库来持久化模型对象

很多时候都需要把模型对象数据永久保存,最自然的方式就是把数据存入数据 库。

在开发期间,可以直接使用内建的数据库来临时保存数据到内存或一个子目录 里。

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 * from products");

6.3. 用 hibernate 持久化对象模型

使用 hibernate 来自动持久化 java 对象到数据库里。

在任何 java 对象增加 @javax.persistence.Entity 注释就可以定义一个 jpa 实 体。play 会自动启动一个 jpa 实体管理器。

@Entity
public class Product {

    public String name;
    public Integer price;
}

注意! 

一个经常发生的错误是使用 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 String name;
    public Integer price;
}

之后使用 Product 实例的简单方法就可以操作 Product 对象:

Product.find("price > ?", 50).fetch();

Product product = Product.findById(2L);

product.save();

product.delete();

6.4. 保持模型 stateless

play 被设计成“什么都不共享”的机制。这个机制用于保持应用是完全无状态 的。这样就允许程序可以同时运行于多个服务器节点。

Play 框架的设计架构就是无状态的。它没有提供服务器端的机制用来维护跨多 个请求的数据。如果确实需要保存这样的数据的话,可以考虑下面几种方案: 

  1.  如果数据很少很简单,可以存储在 flash 或 session 使用域里。但这些域 只能存储小于 4kb 的数据,并且只能是字符串类型的数据。
  2.  使用数据库,比如需要创建一个横跨多个请求的 wizard 对象:
    • 在第一次请求时初始化并持久化对象到数据库中。
    • 把新创建的对象的 ID 存储到 flash 或 session 域中。
    • 在接下来的请求中,使用对象 id 找回数据、更新数据、再次存储 等。
  3. 暂时存储到 Cache 中,比如需要创建一个横跨多个请求的 wizard 对象:
    • 在第一次请求时初始化并持久化对象到 Cache 中。
    • 把新创建的对象的 key 存储到 flash 或 session 域中。
    • 在接下来的请求中,使用正确的 key 找回数据、更新数据、再次存 储等。
    • 在结束最后一次请求后,把对象永久存储到数据库中。

Cache 不是一个可靠的存储位置,在系统未出现故障时应该可以正确操作数据。

但 Cache 是比 Java Servlet session 更好的选择。


前一篇:
后一篇:

发表评论