分类
persistence

在JPA中的定义索引

Defining Indexes in JPA

译者注

原文地址:https://www.baeldung.com/jpa-indexes
实例代码:https://github.com/eugenp/tutorials/tree/master/persistence-modules/java-jpa-3

1. 概述

本文我们将讨论使用JPA的@Index注解来定义索引。在实例中,我们首先讲解如何使用JPA和Hiernage来定义第一个索引。接下来讲解更多关于自定义注解的知识。

2. @Index 注解

数据库索引是一种数据结构,它以增加的写入和存储空间为代价,提高了对表进行数据检索操作的速度。通常来说,它是从某个单表中选择的数据库的副本。我们应该合理的为数据表创建索引来提高数据库的查询性能。

JPA允许我们在代码中使用@Index注解来定义数据表索引。该注解能够实现在创建(更新)数据表时自动为数据表添加索引。

接下来我们看一下如何使定义索引:

2.1. javax.persistence.Index

自JPA2.1版本,JPA增加了java.prsistence.Index来允许我们为数据表创建注解:

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

如上代码所示,@Index注解中仅有columnList属性是必填的。后面我们将具体使用该注解来具体的查看各个属性的用法。

2.2. JPA 和 Hibernate

我们常弄不清JPA与Hibernate的关系。我们知道JPA仅仅是一个规范,它除了做了一些规定提供了一些愿景(也可以说模板、蓝图)以外,其它什么也没做。而要其使愿景便成事实,则需要有人来做实际的工作将其实现。在Spring中使用了Hibernate来完成了这个愿景,你可以参考本文来进一步学习。当然了,除了 Hibernate以外还有其它的JPA实现,比如EclipseLink。所以JPA就好像面向对象思想中的接口,而HibernateEclipseLink则都是这个接口的具体实现

值的注意的是由于JPA添加索引支持比较晚,而在这之前多个ORM框架均有自己支持索引的方法。这使得在JPA统一规范以前,出现了各个ORM各自为政的情况。比如Hibernate就有自己的org.hibernate.annotations.Index注解。所以我们在使用索引注解时,一定上要确认使用的是JPA下的注解 ---- java.prsistence.Index(而不是其他框架中的同名注解)。

简单的了解一些技术背景了,让我们正式开始。

3. 定义@Index

在定义索引前,我们需要了解一些如何定义JPA实体的相关知识。

我们实现一个Student学生实体如下:

/**
 * 学生.
 *
 * @author panjie
 */
@Entity
@Table
public class Student implements Serializable {
  @Id
  @GeneratedValue
  private Long id;

  /**
   * 姓名.
   */
  private String name;

  /**
   * 学号.
   */
  private String sno;

  // setters getters
}

接下来,我们在@Table注解中加入相关的@Index注解。

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

如上我们创建了第一个基于name的索引,此时数据表在初始化时,控制台的日志信息中将对应生成以下Sql代码:

// 中间的值是自动生成的,不会完全一致
[main] DEBUG org.hibernate.SQL -
  create index IDX2gdkcjo83j0c2svhvceabnnoh on Student (name)

接下来我们索引的其他属性和功能。

3.1. 索引名称

不难看出,索引必须有一个名称。默认情况下我们没有指定名称,那么将会使用自动生成的值。
如果我们需要自定义一个名称,只需要修改name属性:

@Index(name = "test_index", columnList = "name")

如代码所示,我们创建了一个名为test_index的索引。
在数据表初始化时,日志就会变成:

[main] DEBUG org.hibernate.SQL -
  create index test_index on Student (name)

此外,我们可以通过在名称中指定模式(schema)的名称,以便在不同的模式(schema)中创建索引:

@Index(name = "schema2.test_index", columnList = "name")

3.2. 在多个列上定义一个@Index

columnList语法如下:

column ::= index_column [,index_column]*
index_column ::= column_name [ASC | DESC]

刚才我们提到,索引是对于一个列的索引,也必须包括列名。然而,我们也可以在单个索引中指定多个列。
方法就是用逗号来分隔名称:

@Index(name = "mulitIndex1", columnList = "name, phoneNumber")

@Index(name = "mulitIndex2", columnList = "phoneNumber, name")

生成的日志:

[main] DEBUG org.hibernate.SQL -
  create index mulitIndex1 on Student (name, phoneNumber)

[main] DEBUG org.hibernate.SQL -
  create index mulitIndex2 on Student (phoneNumber, name)

需要注意的是,如果我们定义多列索引,索引的前后顺序必须相同。如果顺序变化,即使使用的同一组列,也是两个不同的索引,如代码所示。

3.3. @Index 索引排序(Order

我们还可以在列名column_name之后指定ASC(升序)或DESC(降序),来控制索引值的排列顺序:

@Index(name = "mulitSortIndex", columnList = "name, phoneNumber DESC")

日志:

[main] DEBUG org.hibernate.SQL -
  create index mulitSortIndex on Student (name, phoneNumber desc)

3.4. 索引唯一性(Unique

还有一个参数是unique属性,它用来定义索引是否唯一。“唯一”索引确保索引的字段不存储重复值(就像平时常见的ID或者学号等等)。
默认情况下它是false,如果想启用唯一性,可以这样声明:

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

日志:

[main] DEBUG org.hibernate.SQL -
  alter table Student add constraint uniqueIndex unique (firstName)

当我们在某个列上使用唯一索引的时候,就相当于再这个列上添加了一个唯一性约束。它的作用和“在@Column注解上使用约束”是相似的。
但由于@Index可以声明多个列的唯一约束(相当于联合主键,单个列的数据允许重复,只有约束中的所有列的数据都重复,才不被允许),使用@Index要优于使用@Column:

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

3.5. 在单个实体上定义多个@Index索引

到目前为止,我们已经学习了索引的各个参数的功能。
而事实上,我们可以在一个实体上定义多个索引,多个索引的定义方法是——在大括号中重复使用@Index注解:

@Entity
@Table(indexes = {
  @Index(columnList = "firstName"),
  @Index(name = "test_index", columnList = "name"),
  @Index(name = "mulitIndex1", columnList = "name, phoneNumber"),
  @Index(name = "mulitIndex2", columnList = "phoneNumber, name"),
  @Index(name = "mulitSortIndex", columnList = "name, phoneNumber DESC"),
  @Index(name = "uniqueIndex", columnList = "name", unique = true),
  @Index(name = "uniqueMulitIndex", columnList = "name, phoneNumber", unique = true)
})
public class Student implements Serializable

此外,我们还可以为同一组列创建多个索引。

3.6. 主键

当我们谈论索引时,必须要考虑主键。因为被实体管理器EntityManager来管理的每个实体都必须指定一个主键。
一般来说,主键是一种特定类型的唯一索引,与其他索引不同的是,主键不需要用前面介绍的@Index来声明,因为@Id 注解自动实现了这个功能。

3.7. 对于非实体(Entity)对象使用@Index

我们需要知道的是,@Index注解不仅仅能用在表(@Table)上,也能用于某些其他的对象,例如@SecondaryTable(多表联合)、@CollectionTable(集合表)、@JoinTable(连接表)、@TableGenerator(表生成器),这些知识不再详细讲解,请查看javax.persistence JavaDoc

4. 结论

本文中我们讨论了如果使用JPA中的@Index索引,包括更改名称、包含的列、顺序、以及唯一性。后面我们讨论了主键,以及如何在其他对象中声明索引。

简单总结一下:

  1. 索引名称name,不使用名称时会自动生成
  2. 多个列columnList,列的顺序必须一致
  3. 排序ASCDESC
  4. 唯一性unique = true
  5. 主键不需要使用@Index,因为@Id完成了这个功能

本文中的示例可在 GitHub 上找到。