JPA
在 与 JPA 比较 中讨论的问题也适用于 Hibernate。这里的比较更具体地适用于 Hibernate。
在 SQL 中遵守 maxRows
Ebean ORM 始终会在生成的 SQL 中遵守 firstRows / maxRows。一旦你将联接获取包含在 @OneToMany 中,Hibernate 就会停止在 SQL 中实现 maxRows,而是将所有行带回客户端(应用程序服务器)并在那里过滤结果。
这意味着数据库没有机会使用 maxRows(通过 limit offset 子句或类似子句)优化该查询,因此数据库查询执行计划可能非常不同,因为它降低了数据库使用索引的能力并增加了全表扫描的机会。
这也意味着从数据库拉回客户端的数据明显更多。
SQL 笛卡尔积
Ebean ORM 绝不会生成 SQL 笛卡尔积。当你联接获取多个 @OneToMany 或 @ManyToMany 关联时,Hibernate 会生成 SQL 笛卡尔积。
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 的审计中。
Ebean(select/fetch)与 Hibernate JPA fetchgraph 提示(EntityGraph)
Hibernate 不会在属性/列级别上遵循 JPA fetchgraph 提示,这意味着我们无法使用它来优化我们的 SQL 查询。
PagedList / findCount
JPA 和 Hibernate 都没有内置对具有 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 对 Set
和 List
具有不同的语义(包语义)。对于 Hibernate,这往往会促进使用 Set 作为首选的集合类型。
Ebean 不会在 Set
和 List
之间应用不同的语义。
使用 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 约束名称“绝对是垃圾”。