1. 引言

在本篇文章中,我们将深入探讨如何使用 JPA 的 @Index 注解来定义数据库索引。通过具体的示例,你将学会如何在 JPA 和 Hibernate 中创建第一个索引,并进一步了解如何对其进行自定义配置。

2. @Index 注解详解

2.1. 简要回顾:什么是数据库索引?

数据库索引 是一种数据结构,它能显著提升从表中检索数据的速度,代价是增加写入操作的开销和额外的存储空间占用。通常来说,它是从单个表中选取某些列构建的一份“副本”。合理地创建索引可以有效优化持久层性能。

JPA 提供了 @Index 注解,让我们可以直接在代码中定义索引,由框架在生成数据库 schema 时自动创建对应的索引对象。注意,实体类并不强制要求必须包含索引。

2.2. jakarta.persistence.Index

索引支持是在 JPA 2.1 规范中正式加入的,通过 jakarta.persistence.Index 注解实现。该注解允许我们为表定义索引并进行定制化配置:

@Target({})
@Retention(RUNTIME)
public @interface Index {
    String name() default "";
    String columnList();
    boolean unique() default false;
}

如上所示,只有 columnList 是必填项。后续我们会结合示例逐一介绍每个参数的作用。

⚠️ 注意:目前该注解不支持修改默认的索引算法(通常是 B-tree)。

2.3. JPA 与 Hibernate 的关系

我们知道 JPA 只是一个规范,实际运行还需要依赖一个持久化提供者(provider)。Spring Boot 默认使用的是 Hibernate 实现。更多关于 Hibernate 的内容可以参考 这篇文章

由于 JPA 对索引的支持出现得比较晚,在此之前很多 ORM 框架都各自实现了自己的索引机制。比如 Hibernate 就曾提供过 org.hibernate.annotations.Index 注解,但自从 JPA 2.1 支持后,这个注解就被标记为 已废弃(deprecated)。因此,在使用 Hibernate 时,务必优先使用标准的 JPA 注解。

3. 如何使用 @Index 创建索引

接下来我们将逐步演示如何使用 @Index 定义索引,并展示其多种定制方式。

3.1. 初始化模型

首先,我们创建一个简单的实体类 Student

@Entity
@Table
public class Student implements Serializable {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    // getters, setters
}

3.2. 第一个索引

现在我们为 firstName 字段添加索引:

@Table(indexes = @Index(columnList = "firstName"))

执行 schema 生成后,可以看到如下 SQL 输出:

[main] DEBUG org.hibernate.SQL -
  create index IDX2gdkcjo83j0c2svhvceabnnoh on Student (firstName)

✅ 自动生成的索引名称由 Hibernate 决定。

3.3. 自定义索引名称

如果不希望使用默认名称,可以通过 name 属性指定:

@Index(name = "fn_index", columnList = "firstName")

生成的 SQL 语句如下:

[main] DEBUG org.hibernate.SQL -
  create index fn_index on Student (firstName)

还可以指定不同 schema 下的索引名:

@Index(name = "schema2.fn_index", columnList = "firstName")

3.4. 多列索引

我们可以为多个字段创建联合索引,字段之间用逗号分隔:

@Index(name = "multiIndex1", columnList = "firstName, lastName")
@Index(name = "multiIndex2", columnList = "lastName, firstName")

生成的 SQL 如下:

[main] DEBUG org.hibernate.SQL -
  create index multiIndex1 on Student (firstName, lastName)

[main] DEBUG org.hibernate.SQL -
  create index multiIndex2 on Student (lastName, firstName)

⚠️ 注意:字段顺序会影响索引效果。即使字段相同,顺序不同也会创建不同的索引。

3.5. 排序方向控制

在字段名后可以加上 ASCDESC 来指定排序方向:

@Index(name = "multiSortIndex", columnList = "firstName, lastName DESC")

对应 SQL:

[main] DEBUG org.hibernate.SQL -
  create index multiSortIndex on Student (firstName, lastName desc)

如果没有指定,默认为升序(ASC)。

3.6. 唯一索引

通过设置 unique = true 可以创建唯一索引:

@Index(name = "uniqueIndex", columnList = "firstName", unique = true)

生成的 SQL 语句如下:

alter table Student add constraint uniqueIndex unique (firstName)

这类似于在 @Column 上使用 unique = true,但 @Index 的优势是可以对多个字段组合设置唯一性约束:

@Index(name = "uniqueMultiIndex", columnList = "firstName, lastName", unique = true)

3.7. 为单个实体定义多个索引

我们可以为同一个实体定义多个索引,写法如下:

@Entity
@Table(indexes = {
  @Index(columnList = "firstName"),
  @Index(name = "fn_index", columnList = "firstName"),
  @Index(name = "multiIndex1", columnList = "firstName, lastName"),
  @Index(name = "multiIndex2", columnList = "lastName, firstName"),
  @Index(name = "multiSortIndex", columnList = "firstName, lastName DESC"),
  @Index(name = "uniqueIndex", columnList = "firstName", unique = true),
  @Index(name = "uniqueMultiIndex", columnList = "firstName, lastName", unique = true)
})
public class Student implements Serializable

✅ 同一组字段也可以创建多个不同配置的索引。

3.8. 主键索引

主键本质上也是一种唯一索引。当我们使用 @Id 注解时,JPA 会自动为主键字段创建索引,无需显式声明。

3.9. 在非主表中定义索引

除了 @Table 外,我们还可以在以下注解中定义索引:

  • @SecondaryTable
  • @CollectionTable
  • @JoinTable
  • @TableGenerator

这些场景不在本文讨论范围内,详情请查阅 jakarta.persistence JavaDoc

4. 总结

在这篇文章中,我们介绍了如何使用 JPA 的 @Index 注解来定义数据库索引。我们从基础概念出发,逐步展示了如何通过设置名称、字段列表、排序方式以及唯一性等属性来自定义索引行为。最后还提到了主键索引及在其他注解中的应用。

掌握这些技巧可以帮助你在设计实体时更灵活地控制数据库性能表现,避免踩坑。


原始标题:Defining Indexes in JPA | Baeldung