JPA

与 JPA 比较 中讨论的问题也适用于 Hibernate。这里的比较更具体地适用于 Hibernate。

在 SQL 中遵守 maxRows

Ebean ORM 始终会在生成的 SQL 中遵守 firstRows / maxRows。一旦你将联接获取包含在 @OneToMany 中,Hibernate 就会停止在 SQL 中实现 maxRows,而是将所有行带回客户端(应用程序服务器)并在那里过滤结果。

这意味着数据库没有机会使用 maxRows(通过 limit offset 子句或类似子句)优化该查询,因此数据库查询执行计划可能非常不同,因为它降低了数据库使用索引的能力并增加了全表扫描的机会。

这也意味着从数据库拉回客户端的数据明显更多。

使用 maxRows 联接 @OneToMany

在这种情况下,Hibernate 不会遵守 maxRows

SQL 笛卡尔积

Ebean ORM 绝不会生成 SQL 笛卡尔积。当你联接获取多个 @OneToMany 或 @ManyToMany 关联时,Hibernate 会生成 SQL 笛卡尔积。

联接获取多个 @OneToMany

Hibernate 会生成 SQL 笛卡尔积。将其与 Ebean 的方法进行比较

LazyInitialisationException

超出上下文范围的延迟加载

Ebean 和 EclipseLink 可以做到,Hibernate 会抛出 LazyInitialisationException

Hibernate 不允许延迟加载超出其会话范围的末尾,而是会抛出 LazyInitialisationException

Ebean 允许延迟加载超出初始范围

  • 根据 JPA 规范,使用已提交的读取事务隔离级别。这意味着当我们使用另一个 JDBC 事务进行延迟加载时,与保持事务打开(通常通过“打开视图中的会话”由 Hibernate 要求)相比,没有实际差异。
  • Ebean 的实体 Bean 具有对加载上下文的引用,这使得后续延迟加载能够使用相同的持久性上下文执行,并且还支持批量延迟加载。这意味着延迟加载仍然会生成一致的对象图(就像急切加载一样)。
  • 如果你想防止使用 Ebean 进行延迟加载,你可以执行 query.setDisableLazyLoading(true)。当你想要使用部分填充的 Bean 并将其提供给基于反射的工具(将其转换为 JSON 或 DTO 等)时,这很有用。
使用 Ebean 延迟加载只需轻而易举

 

在视图中打开会话

作为 Ebean 支持延迟加载(超出事务范围)的副产品,Ebean 不需要“在视图中打开会话”模式,该模式有时与 Hibernate 一起使用。

与 Hibernate 一起使用的“在视图中打开会话”的效果是相对于 Ebean 较长时间保持事务处于打开状态。许多人认为这种模式是一种反模式,而重点在于确保服务层代码已加载实体上所需的一切(而这是我们无需使用 Ebean 编写的代码)。

 

SQL2011 @History 与 Hibernate Envers

Ebean 的 @History 是一种以数据库为中心的映射到 SQL2011 的方法。Hibernate Envers 是一种以应用程序为中心的方法,这意味着与 @History 不同,批量更新和外部更新不会包含在 Envers 的审计中。

与 Hibernate Envers 的比较

与 Hibernate Envers 采用的方法进行比较

 

Ebean(select/fetch)与 Hibernate JPA fetchgraph 提示(EntityGraph)

Hibernate 不会在属性/列级别上遵循 JPA fetchgraph 提示,这意味着我们无法使用它来优化我们的 SQL 查询。

 

PagedList / findCount

JPA 和 Hibernate 都没有内置对具有 findCount 的 PagedList 的支持 - 也就是说,具有要执行的单个查询,并且该查询会自动转换为适当且经过优化的“查找总行数”查询。

FindCount 和 PagedList

Ebean 的 findCount 查询的工作原理以及如何使用 PagedList

 

findEach 与滚动

JPA 没有针对大型查询支持的标准方法,但 Hibernate 有一个“滚动”查询。

Ebean 的 findEach() 使用持久性上下文的每个对象图范围工作,并且还会自动调整 JDBC fetchSize 以用于游标/滚动,以便 JDBC 驱动程序(尤其是 MySql 和 Postgres)不会将所有结果拉到客户端,并且对于 MySql 在单独的事务中运行 findEach() 查询(以便我们可以执行查询联接并迭代复杂的对象图)。

在使用 Hibernate“滚动”查询时,你需要确保

  • 定期从会话中“驱逐”Bean 或使用 StatelessSession。
  • 在查询中设置 fetchSize
  • 如果你正在使用 MySql,则需要使用 FORWARD_ONLY 和 READ_ONLY 选项创建语句,将 Integer.MIN 用作缓冲区 fetchSize,并且如果你想在查询滚动时执行其他查询,则不使用相同的 java.sql.Connection

 

Set 与 List

Hibernate 对 SetList 具有不同的语义(包语义)。对于 Hibernate,这往往会促进使用 Set 作为首选的集合类型。

Ebean 不会在 SetList 之间应用不同的语义。

使用 Set 的问题在于它暗示使用 hashCode()/equals(),而 hashCode()/equals() 的实现对于会发生突变并且不总是具有 @Id 值的实体 bean 来说并不完美(例如,当 @Id 值不是通过生成值填充的,因此直到保存后才填充,因此 @Id 值不能用于 hashCode/equals 实现中)。

对于 Ebean,我想推广使用 List 而优先于 Set,以避免与对发生突变的实体 bean 进行 hashCode()/equals() 实现相关的任何混淆。

 

映射

命名约定

Ebean 的默认 UnderscoreNamingConvention 匹配 Hibernate 的 ImprovedNamingStrategy,但不匹配基于 JPA 标准的命名约定(该命名约定具有混合大小写的列命名和下划线)。请注意,Spring boot 自动将 Hibernate 配置为使用 ImprovedNamingStrategy。

javax.validation.constraints NotNull 和 Size

Ebean 和 Hibernate 都使用 @NotNull@Size 验证注释进行映射。

@ManyToOne

JPA 将所有 @ManyToOne 默认为 EAGER 提取。当使用 Hibernate 时,我们几乎肯定不希望这样做(因为那些 Eager 提取类型随后不能通过 JPQL 或 fetchgraph 提示变为延迟提取),因此通常情况下,对于 Hibernate,我们希望大多数 @ManyToOne 通过 ... @ManyToOne(fetch = FetchType.LAZY) 显式变为延迟提取。

对于 Ebean,我们使用查询语言定义“提取什么”,并且不需要在 @ManyToOne 上指定 FetchType.LAZY。

UUID 类型

Ebean 自动将 UUID 映射到 Postgres 和 H2 的本机类型 - 因此 UUID 就像我们希望的那样工作。

Hibernate 支持通过 org.hibernate.annotations.Type 将 UUID 映射到 Postgres 的本机类型 - @Type(type="pg-uuid")。这里的缺点是它不适用于 H2 上的 UUID(而是映射到二进制),这意味着 SQL 测试脚本不会使用文本 UUID 值针对 H2 执行(因此,在此处针对 H2 进行测试时会遇到一些麻烦)。

没有 @Id 的实体

Ebean 允许实体在没有 @Id 的情况下进行映射。这些实体通常基于 SQL 或数据库视图,并用于报告目的。Ebean 允许这样做,并且这些实体 Bean 绕过持久性上下文,因为它们没有 @Id 值。

对于 Hibernate,我们会考虑使用不同的方法,可能是 DTO 查询。

@View - 基于视图的实体

Ebean 通过 @View 明确支持基于视图的实体。这考虑了从数据库迁移和测试角度创建视图的 DDL,以及基于底层表依赖项的 L2 缓存。

DDL 外键和唯一约束

Hibernate 当前生成外键名称,如 FK_edi14sijwrl3p2sf41ls3svkm,以及唯一约束名称,如 UK_ajysu81d17lesquo7uqosrtah

Ebean 针对外键名称和唯一约束名称制定了命名约定,其中包括表和列名称。当名称受到极大限制(DB2 和 Oracle)时,Ebean 会使用“元音去除器”和修剪,以便仍然生成不错的外键和唯一约束名称。作为 DBA,我认为那些 Hibernate 约束名称“绝对是垃圾”。