概述

活动记录是一种模式,最初由 Martin Fowler 命名,它并非 Ebean 特有的,而是一种在许多持久性框架中常见的模式。

一般来说,这意味着实体 Bean 本身具有 save()delete() 的功能。你可以将此与传统的 DAO 方法进行对比,在该方法中,实体 Bean 会被传递给 DAO 以进行保存和删除。

Play 框架

对于 Ebean,活动记录模式是从 Play 框架继承而来的。Ebean 中用于实现活动记录模式的原始 Model 和 Finder 实现源自 Play 框架,并且该代码已迁移到 Ebean 中。

批评

对活动记录模式的两大主要批评是

  • 合并关注点 - “模型”和“如何持久化”紧密结合
  • 可测试性 - 理论上,活动记录更难测试

需要注意的是,Ebean 通过 ebean-mocker 提供了一种测试机制,以便可以对保存和查找的活动记录模式进行全面测试。

Rob 的观点:JPA 和活动记录

我的观点是,JPA 不太适合活动记录模式。这是基于我对 Grails/GORM/Hibernate 的个人经验。具体来说,GORM 实际上向开发者隐藏了 Hibernate Session 的存在。我的经验表明,这在简单的情况下效果很好,但在更复杂的情况下,开发者会发现需要明确了解和处理 Session/EntityManager 对象。

活动记录的 save() delete() 语义与 JPA 的底层附加/分离/刷新语义不匹配,并且在更复杂的情况下,这种不匹配会显现出来(抽象泄漏)。这通常会导致编码风格和思维方式的改变,以及包含这两种风格的代码库。

Rob 的观点:Ebean 和活动记录

Ebean 是一个“无会话”ORM,与 JPA 不同,它没有附加/分离语义。这使得 Ebean 非常适合活动记录模式,因为活动记录 API/抽象与底层 EbeanServer 之间没有不匹配。

使用 Ebean 处理复杂的持久性案例意味着您希望/需要完全控制 JDBC 批处理机制。在这些更复杂的情况下,活动记录 save() 和 delete() 仍然按预期和需要工作,但您还可以通过事务(以编程方式或通过 @Transactional)额外调整 JDBC 批处理控制。也就是说,在复杂的情况下无需更改样式。

测试

Ebean 提供了 ebean-mocker 项目,该项目支持在使用活动记录模式时进行测试要求。它支持在持久化和查找中为 EbeanServer 使用测试替身,并提供 DelegateEbeanServer 作为持久化和查找的开箱即用的良好测试替身。

DelegateEbeanServer 提供

  • 捕获持久化调用,断言已保存的 Bean 等
  • findById 存根/模拟响应
  • 静态 Finder 存根/模拟测试替身
  • SQL 捕获

Rob 的观点:活动记录的好处

对于某些人来说,活动记录会合并关注点,这结束了对话,这是可以的。

不会错过可测试性

就我个人而言,当我将所谓的传统 DAO/Inject 方法与活动记录进行比较时,我首先注意到我并没有失去可测试性 - 使用 Ebean 的活动记录与 DAO/Inject 样式一样具有可测试性。

简单的事情保持简单

其次,活动记录的主要优点是“简单的事情”保持简单。我的意思是 save()、delete()、获取引用、按 ID 查找和按唯一键查找在代码中都非常简单且非常干净。在 DAO/Inject 代码中,我看到的是对于每种类型的实体 Bean,您都会注入一个 DAO - 对于处理 4 种不同实体 Bean 类型的服务,您最终会注入 4 个 DAO。特别是对于构造函数注入,注入 DAO 很快就会导致相对丑陋的构造函数和“解决方法”。

使用活动记录,简单的事情保持简单

总体而言,我会选择活动记录

如果有人问我是在活动记录样式还是传统 DAO/Inject 样式之间进行选择,我会建议使用活动记录样式,因为它会产生非常简洁干净的代码。我们不会错过可测试性(由于 ebean-mocker),而且我认为实体 Bean 具有 save() delete() 方法(混合关注点)是可以的。

模型

当您的实体 Bean 扩展 io.ebean.Model 对象时,它们会继承一些方便的方法 save()、delete() 等。在内部调用这些方法时,将使用默认 EbeanServer 来执行 save() 和 delete()。

Ebean Model 提供以下方便的方法

  • save() - 保存实体
  • update() - 显式更新实体
  • insert() - 显式插入实体
  • delete() - 删除实体
  • refresh() - 从数据库刷新实体
  • markAsDirty() - 用于在未修改的 bean 更新时更新版本属性
  • update(String server) - 使用指定的 Ebean 服务器更新实体
  • insert(String server)- 使用指定的服务器插入实体
  • delete(String server)- 使用指定的服务器删除实体

示例:扩展模型

/**
 * Extend Model to get the save(), delete() etc
 * methods on the bean itself (active record style).
 */
@Entity
@Table(name="customer")
public class Customer extends Model {

  @Id
  Long id;

  String name;

  Date registered;

  String comments;

  ...

  

示例:save()

Customer customer = new Customer();
customer.setName("Rob");

customer.save();

查找器

查找器提供了一些便捷查询方法,还建议了一种组织“查找器”代码的方法。

示例:按 ID 查找

Customer customer = Customer.find.byId(42);

示例:获取引用

Customer customer = Customer.find.ref(42);

示例:按 ID 删除

Customer.find.deleteById(42);

示例:查找位置

List<Customer> customers =
    Customer.find
        .where().ilike("name", "rob%")
        .findList();