Unique Field in MongoDB Document in Spring Data – Spring Data中MongoDB文档的唯一字段

最后修改: 2022年 6月 27日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’re going to learn how to define a unique field in MongoDB using Spring Data. Unique fields are an essential part of the DB design. They guarantee consistency and performance at the same time, preventing duplicate values where there shouldn’t be.

在本教程中,我们将学习如何在MongoDB中使用Spring Data定义一个唯一字段。唯一字段是 DB 设计的一个重要部分。它们同时保证了一致性和性能,防止在不应该存在的地方出现重复的值。

2. Configuration

2.配置

Unlike relational databases, MongoDB does not offer the option to create constraints. Therefore, our only option is to create unique indexes. However, automatic index creation in Spring Data is turned off by default. Firstly, let’s go ahead and turn it on in our application.properties:

与关系型数据库不同,MongoDB 不提供创建约束的选项。因此,我们唯一的选择是创建唯一的索引但是,Spring Data 中的自动索引创建默认是关闭的。首先,让我们继续前进,在我们的application.properties中打开它。

spring.data.mongodb.auto-index-creation=true

With that configuration, indexes will be created on boot time if they do not yet exist. But, we have to remember that we cannot create a unique index after we already have duplicate values. This would result in an exception being thrown during the startup of our application.

在这种配置下,如果索引还不存在,将在启动时创建。但是,我们必须记住,我们不能在已经有重复的值之后再创建一个唯一的索引。这将导致我们的应用程序在启动时抛出一个异常。

3. The @Indexed Annotation

3.@Indexed注释

The @Indexed annotation allows us to mark fields as having an index. And since we configured automatic index creation, we won’t have to create them ourselves. By default, an index is not unique. Therefore, we have to turn it on via the unique property. Let’s see it in action by creating our first example:

@Indexed注解允许我们将字段标记为具有索引。由于我们配置了自动创建索引,所以我们不必自己创建索引。默认情况下,索引是不唯一的。因此,我们必须通过unique属性将其打开。让我们通过创建我们的第一个例子来看看它的作用。

@Document
public class Company {
    @Id
    private String id;

    private String name;

    @Indexed(unique = true)
    private String email;

    // getters and setters
}

Notice we can still have our @Id annotation, which is completely independent of our index. And that’s all we need to have a document with a unique field. As a result, if we insert more than one document with the same email, it will result in a DuplicateKeyException:

注意我们仍然可以有我们的@Id注释,它完全独立于我们的索引。这就是我们所需要的具有唯一字段的文档。因此,如果我们插入多个具有相同email的文档,将导致DuplicateKeyException

@Test
public void givenUniqueIndex_whenInsertingDupe_thenExceptionIsThrown() {
    Company a = new Company();
    a.setName("Name");
    a.setEmail("a@mail.com");

    companyRepo.insert(a);

    Company b = new Company();
    b.setName("Other");
    b.setEmail("a@mail.com");
    assertThrows(DuplicateKeyException.class, () -> {
        companyRepo.insert(b);
    });
}

This approach is useful when we want to enforce uniqueness but still have a unique ID field generated automatically.

当我们想强制执行唯一性,但仍然有一个自动生成的唯一ID字段时,这种方法很有用。

3.1. Annotating Multiple Fields

3.1.注释多个字段

We can also add the annotation to multiple fields. Let’s go ahead and create our second example:

我们也可以将注解添加到多个字段。让我们继续并创建我们的第二个例子。

@Document
public class Asset {
    @Indexed(unique = true)
    private String name;

    @Indexed(unique = true)
    private Integer number;
}

Notice we’re not explicitly setting @Id on any field. MongoDB will still set an “_id” field for us automatically, but it won’t be accessible to our application. But, we cannot put the @Id along with a @Indexed annotation marked as unique on the same field. It would throw an exception when the application tried to create the index.

注意我们没有在任何字段上明确设置@Id。MongoDB仍将为我们自动设置一个”_id“字段,但我们的应用程序将无法访问它。但是,我们不能将@Id和标记为@Indexed注解一起放在同一个字段上。当应用程序试图创建索引时,它会抛出一个异常。

Also, now we have two unique fields. Note that this does not mean that it’s a compound index. Consequently, multiple inserts of the same value for any of the fields will result in a duplicate key. Let’s test it:

另外,现在我们有两个唯一的字段。注意,这并不意味着它是一个复合索引。因此,多次插入任何字段的相同值都会导致键的重复。让我们来测试一下。

@Test
public void givenMultipleIndexes_whenAnyFieldDupe_thenExceptionIsThrown() {
    Asset a = new Asset();
    a.setName("Name");
    a.setNumber(1);

    assetRepo.insert(a);

    assertThrows(DuplicateKeyException.class, () -> {
        Asset b = new Asset();
        b.setName("Name");
        b.setNumber(2);

        assetRepo.insert(b);
    });

    assertThrows(DuplicateKeyException.class, () -> {
        Asset b = new Asset();
        b.setName("Other");
        b.setNumber(1);

        assetRepo.insert(b);
    });
}

If we want only the combined values to form a unique index, we have to create a compound index.

如果我们只想让组合的值形成一个唯一的索引,我们必须创建一个复合索引。

3.2. Using a Custom Type as an Index

3.2.使用自定义类型作为索引

Similarly, we can annotate a field of a custom type. That will achieve the effect of a compound index. Let’s start with a SaleId class to represent our compound index:

类似地,我们可以注解一个自定义类型的字段。这将实现复合索引的效果。让我们从一个SaleId类开始,以表示我们的复合索引。

public class SaleId {
    private Long item;
    private String date;

    // getters and setters
}

Let’s now create our Sale class to make use of it:

现在让我们创建我们的Sale类来利用它。

@Document
public class Sale {
    @Indexed(unique = true)
    private SaleId saleId;

    private Double value;

    // getters and setters
}

Now, every time we try to add a new Sale with the same SaleId, we’ll get a duplicate key. Let’s test it:

现在,每次我们试图添加一个新的Sale,并具有相同的SaleId,我们会得到一个重复的键。让我们来测试一下。

@Test
public void givenCustomTypeIndex_whenInsertingDupe_thenExceptionIsThrown() {
    SaleId id = new SaleId();
    id.setDate("2022-06-15");
    id.setItem(1L);

    Sale a = new Sale(id);
    a.setValue(53.94);

    saleRepo.insert(a);

    assertThrows(DuplicateKeyException.class, () -> {
        Sale b = new Sale(id);
        b.setValue(100.00);

        saleRepo.insert(b);
    });
}

This approach has the advantage of keeping the index definition separate. This allows us to include or remove new fields from SaleId without having to recreate or update our index. It’s also very similar to a composite key. But, indexes differ from keys because they can have one null value.

这种方法的优点是将索引定义分开。这使我们能够从SaleId中包含或删除新的字段,而不必重新创建或更新我们的索引。它也与复合键非常相似。但是,索引与键不同,因为它们可以有一个空值。

4. The @CompoundIndex Annotation

4.@CompoundIndex注释

To have a unique index comprised of multiple fields without a custom class, we have to create a compound index. For that, we use the @CompoundIndex annotation directly in our class. This annotation contains a def property that we’ll use to include the fields we need. Let’s create our Customer class defining a unique index for the storeId and number fields:

为了拥有一个由多个字段组成的独特索引而不需要自定义类,我们必须创建一个复合索引。为此,我们直接在我们的类中使用@CompoundIndex注解。该注解包含一个def属性,我们将用它来包含我们需要的字段。让我们创建我们的Customer类,为storeIdnumber字段定义一个唯一索引。

@Document
@CompoundIndex(def = "{'storeId': 1, 'number': 1}", unique = true)
public class Customer {
    @Id
    private String id;

    private Long storeId;
    private Long number;
    private String name;

    // getters and setters
}

This differs from @Indexed on multiple fields. This approach only results in a DuplicateKeyException if we try inserting a customer with the same storeId and number values:

这与多个字段上的@Indexed不同。这种方法只在我们尝试插入一个具有相同storeIdnumber值的客户时,才会导致DuplicateKeyException

@Test
public void givenCompoundIndex_whenDupeInsert_thenExceptionIsThrown() {
    Customer customerA = new Customer("Name A");
    customerA.setNumber(1l);
    customerA.setStoreId(2l);

    Customer customerB = new Customer("Name B");
    customerB.setNumber(1l);
    customerB.setStoreId(2l);

    customerRepo.insert(customerA);

    assertThrows(DuplicateKeyException.class, () -> {
        customerRepo.insert(customerB);
    });
}

With this approach, we have the advantage of not having to create another class just for our index. Also, it’s possible to add the @Id annotation to a field from the compound index definition. However, different from @Indexed, it won’t result in an exception.

通过这种方法,我们的优势在于不必为我们的索引创建另一个类。此外,我们还可以从复合索引定义中为一个字段添加@Id注解。然而,与@Indexed不同,它不会导致异常。

5. Conclusion

5.总结

In this article, we learned how to define unique fields for our documents. Consequently, we learned that our only option is to use unique indexes. Also, using Spring Data, we can easily configure our application to automatically create our indexes. And, we saw the many ways to use the @Indexed and @CompoundIndex annotations.

在这篇文章中,我们学习了如何为我们的文档定义唯一字段。随后,我们了解到我们唯一的选择是使用唯一的索引。而且,使用Spring Data,我们可以轻松地配置我们的应用程序来自动创建索引。而且,我们看到了使用@Indexed@CompoundIndex注释的多种方式。

And as always, the source code is available over on GitHub.

一如既往,源代码可在GitHub上获得。