Hibernate

Hibernate 和 Spring Data JPA 中的 N+1 问题

1、概览 Spring JPA 和 Hibernate 为无缝数据库通信提供了强大的工具。不过,由于客户端将更多控制权委托给了框架,因此生成的查询可能远非最佳。 本文将带你了解使用 Spring JPA 和 Hibernate 时常见的 N+1 问题,以及可能导致该问题的不同情况。 2、社交媒体平台 为了更好地将问题形象化,我们需要概述实体之间的关系。以一个简单的社交网络平台为例。这里只有用户(User)和帖子(Post): 我们在图表中使用了 Iterable,并且我们将为每个示例提供具体的实现:List 或 Set。 为了测试请求的数量,我们将使用一个专用库,而不是检查日志。不过,我们会参考日志,以便更好地了解请求的结构。 如果在每个示例中没有明确指定关系的获取类型(Fetch Type),则默认情况下假定为默认值。所有的一对一关系都使用急切加载(Eager Fetch),而一对多关系则使用延迟加载(Lazy)。此外,代码示例中使用了 Lombok 来减少代码中的冗余。 3、N+1 问题 N+1 问题指的是,对于单个请求(例如检索用户),会对每个用户发出额外请求,以获取其信息。虽然这个问题通常与懒加载有关,但并非总是如此。 任何类型的关系都可能出现这种问题。不过,它通常出现在多对多或一对多关系中。 3.1、延迟加载 首先,来看看懒加载是如何导致 N+1 问题的,示例如下: @Entity public class User { @Id private Long id; private String username; private String email; @OneToMany(cascade = CascadeType.ALL, mappedBy = "author") protected List<Post> posts; // 构造函数/getter/setter } User 与 Post 之间是一对多的关系。这意味着每个 User 都有多个 Post。我们没有明确确定字段的 Fetch 策略。策略是从注解中推断出来的。如前所述,@OneToMany 默认采用 Lazy Fetch 策略:

Hibernate 异常 QueryException: “named parameter not bound”.

1、概览 本文将带你了解如何解决 Hibernate 异常 QueryException: “named parameter not bound”。 2、理解异常 简而言之,在将 Hibernate 查询转换为 SQL 时,由于语法无效,Hibernate 会抛出 QueryException 来提示错误。“named parameter not bound” 表明 Hibernate 无法绑定特定查询中指定的命名参数。 通常情况下,命名参数的前缀是冒号(:),后面是一个占位符,表示在执行查询之前需要设置的实际值: SELECT p FROM Person p WHERE p.firstName = :firstName; 造成异常的最常见原因之一是忘记为 Hibernate 查询中的命名参数赋值。 3、重现异常 理解了导致异常的原因后,通过一个实际的例子来重现这个异常。 创建如下 Person 实体类: @Entity public class Person { @Id private int id; private String firstName; private String lastName; // 标准的 Get、Set } 如上,@Entity 注解表示类是一个实体,它映射了数据库中的一个表。此外,@Id 表示 id 属性代表主键。 现在,创建一个带有命名参数的 Hibernate 查询,并假装忘记为参数设置值: @Test void whenNotSettingValueToNamedParameter_thenThrowQueryException() { Exception exception = assertThrows(QueryException.

将 Hibernate 代理对象转换为实际的实体对象

1、概览 本文将带你了解 Hibernate 是在什么时候创建代理对象的,代理对象有啥用?以及如何把 Hibernate 的代理对象转换为真正的实体对象。 2、Hibernate 何时创建代理对象? Hibernate 使用代理对象来实现懒加载。 有如下 PaymentReceipt 和 Payment 实体: @Entity public class PaymentReceipt { ... @OneToOne(fetch = FetchType.LAZY) private Payment payment; ... } @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Payment { ... @ManyToOne(fetch = FetchType.LAZY) protected WebUser webUser; ... } 加载这两个实体中的任何一个,都会导致 Hibernate 为关联字段使用 FetchType.LAZY 创建一个代理对象。 创建并运行集成测试: @Test public void givenPaymentReceipt_whenAccessingPayment_thenVerifyType() { PaymentReceipt paymentReceipt = entityManager.find(PaymentReceipt.class, 3L); Assert.assertTrue(paymentReceipt.getPayment() instanceof HibernateProxy); } 测试代码如上,加载了一个 PaymentReceipt,并验证了 payment 对象不是 CreditCardPayment 的实例,而是一个 HibernateProxy 对象。

使用 Spring 和 Hibernate 进行表分区

简介 本文将带你了解如何使用 Spring 和 Hibernate 实现表分区。 表分区的目标是将一个大型表分割为多个较小的分区表,以便关联的表和索引记录可以放入内存缓冲池,从而实现更高效的查找或扫描操作。 使用 PostgreSQL 进行表分区 PostgreSQL 为 表分区 提供了三种策略: 列表分区(List Partitioning) 范围分区(Range Partitioning) Hash 分区(Hash Partitioning) 本例使用列表分区,按大洲来划分数据表。 例如,users 分区如下: CREATE TABLE users ( id bigint NOT NULL, first_name varchar(255), last_name varchar(255), registered_on timestamp(6), partition_key varchar(255), PRIMARY KEY (id, partition_key) ) PARTITION BY LIST (partition_key) CREATE TABLE users_asia PARTITION OF users FOR VALUES IN ('Asia') CREATE TABLE users_africa PARTITION OF users FOR VALUES IN ('Africa') CREATE TABLE users_north_america PARTITION OF users FOR VALUES IN ('North America') CREATE TABLE users_south_america PARTITION OF users FOR VALUES IN ('South America') CREATE TABLE users_europe PARTITION OF users FOR VALUES IN ('Europe') CREATE TABLE users_australia PARTITION OF users FOR VALUES IN ('Australia') posts 表分区如下: