概述
活动记录是一种模式,最初由 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();