JPA
在 与 JPA 的比较 中讨论的问题也适用于 Hibernate。此处比较更具体地适用于 Hibernate。
在 SQL 中遵守 maxRows
Ebean ORM 始终会在生成的 SQL 中遵守 firstRows / maxRows。一旦您将连接获取包含到 @OneToMany 中,Hibernate 便会停止在 SQL 中实现 maxRows,而是将所有行带回客户端(应用程序服务器)并在那里过滤结果。
这意味着数据库无法使用 maxRows(通过限制偏移子句或类似方式)优化该查询,因此数据库查询执行计划可能因降低数据库使用索引的能力和增加全表扫描的机会而大不相同。
这也意味着从数据库拉回客户端的数据明显更多。
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 有时使用的“在视图中打开会话”模式。
与 Ebean 相比,“在视图中打开会话”用于 Hibernate 会产生长时间保持事务打开的效果。许多人认为这种模式是一种反模式,相反,重点在于确保服务层代码已加载实体所需的一切(这是我们无需使用 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 或使用无状态会话。
- 在查询上设置 fetchSize
- 如果您使用 MySql,则需要使用 FORWARD_ONLY 和 READ_ONLY 选项创建 Statement,使用 Integer.MIN 作为缓冲区 fetchSize,并且如果您希望在查询滚动时执行其他查询,则不要使用相同的 java.sql.Connection
Set 与 List
Hibernate 对 Set
和 List
具有不同的语义(bag 语义)。对于 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 或 DB 视图,并用于报告目的。Ebean 允许这样做,并且这些实体 bean 绕过持久性上下文,因为它们没有 @Id 值。
使用 Hibernate 时,我们会考虑使用不同的方法,可能是 DTO 查询。
@View - 基于视图的实体
Ebean 通过 @View 明确支持基于视图的实体。这考虑了从数据库迁移和测试角度创建视图的 DDL,以及基于底层表依赖项的 L2 缓存。
DDL 外键和唯一约束
Hibernate 当前生成外键名称,如 FK_edi14sijwrl3p2sf41ls3svkm
,以及唯一约束名称,如 UK_ajysu81d17lesquo7uqosrtah
。
Ebean 具有一个命名约定,用于外键名称和唯一约束名称,其中包括表和列名称。当名称非常受限(DB2 和 Oracle)时,Ebean 使用“元音去除器”和修剪,以便仍然生成体面的外键和唯一约束名称。我作为数据库管理员认为那些 Hibernate 约束名称是“绝对的裤子!”。