UUID as Entity ID in MongoDB – UUID作为MongoDB中的实体ID

最后修改: 2022年 7月 3日

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

1. Overview

1.概述

By default, the MongoDB Java driver generates IDs of the type ObjectId. Sometimes, we may want to use another type of data as the unique identifier of an object, such as a UUID. However, the MongoDB Java driver can’t generate UUIDs automatically.

默认情况下,MongoDB Java 驱动程序会生成类型为 ObjectId 的 ID。有时,我们可能希望使用其他类型的数据作为对象的唯一标识符,例如UID。然而,MongoDB Java 驱动程序不能自动生成 UUID

In this tutorial, we’ll look at three ways to generate UUIDs with the MongoDB Java driver and Spring Data MongoDB.

在本教程中,我们将了解使用 MongoDB Java 驱动程序和Spring Data MongoDB生成 UUID 的三种方法。

2. Common Points

2.共同点

It’s quite rare for an application to manage only one type of data. To simplify the management of the IDs in our MongoDB database, it’s easier to implement an abstract class that will define the ID of all our Document classes.

对于一个应用程序来说,只管理一种类型的数据是相当罕见的。为了简化我们的MongoDB数据库中的ID管理,实现一个抽象类比较容易,它将定义我们所有Document类的ID。

public abstract class UuidIdentifiedEntity {

    @Id   
    protected UUID id;    

    public void setId(UUID id) {

        if (this.id != null) {
            throw new UnsupportedOperationException("ID is already defined");
        }

        this.id = id;
    }

    // Getter
}

For the examples in this tutorial, we’ll assume that all classes persisted in the MongoDB database inherited from this class.

在本教程的例子中,我们将假设所有在MongoDB数据库中持久化的类都继承自这个类。

3. Configuring UUID Support

3.配置UUID支持

To allow storage of UUIDs in MongoDB, we must configure the driver. This configuration is very simple and only tells the driver how to store UUIDs in the database. We must handle this carefully if several applications use the same database.

为了允许在MongoDB中存储UUID,我们必须对驱动进行配置。这个配置非常简单,只是告诉驱动程序如何在数据库中存储UUID。如果有几个应用程序使用同一个数据库,我们必须小心处理。

All we have to do is to specify the uuidRepresentation parameter in our MongoDB client at startup:

我们所要做的就是在启动时在我们的MongoDB客户端指定uuidRepresentation参数

@Bean
public MongoClient mongo() throws Exception {
    ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/test");
    MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
      .uuidRepresentation(UuidRepresentation.STANDARD)
      .applyConnectionString(connectionString).build();
    return MongoClients.create(mongoClientSettings);
}

If we use Spring Boot, we can specify this parameter in our application.properties file:

如果我们使用Spring Boot,我们可以在application.properties文件中指定这个参数。

spring.data.mongodb.uuid-representation=standard

4. Using Lifecycle Events

4.使用生命周期事件

The first method to handle the generation of UUID is by using Spring’s lifecycle events. With MongoDB entities, we can’t use JPA annotations @PrePersist and so on. Therefore, we have to implement event listener classes registered in the ApplicationContext. To do so, our classes must extend the Spring’s AbstractMongoEventListener class:

处理UUID生成的第一种方法是通过使用Spring的生命周期事件。对于MongoDB实体,我们不能使用JPA注解@PrePersist等。因此,我们必须实现在ApplicationContext中注册的事件监听器类。为此,我们的类必须扩展 Spring 的 AbstractMongoEventListener class

public class UuidIdentifiedEntityEventListener extends AbstractMongoEventListener<UuidIdentifiedEntity> {
    
    @Override
    public void onBeforeConvert(BeforeConvertEvent<UuidIdentifiedEntity> event) {
        
        super.onBeforeConvert(event);
        UuidIdentifiedEntity entity = event.getSource();
        
        if (entity.getId() == null) {
            entity.setId(UUID.randomUUID());
        } 
    }    
}

In this case, we’re using the OnBeforeConvert event, which is triggered before Spring converts our Java object to a Document object and sends it to the MongoDB driver.

在这种情况下,我们使用OnBeforeConvert事件,该事件在Spring将我们的Java对象转换为Document对象并将其发送至MongoDB驱动程序之前被触发。

Typing our event to catch UuidIdentifiedEntity class allows handling all subclasses of this abstract supertype. Spring will call our code as soon as an object using a UUID as ID is converted.

将我们的事件输入到捕获UuidIdentifiedEntity类中,可以处理这个抽象超类型的所有子类。一旦使用UUID作为ID的对象被转换,Spring将立即调用我们的代码。

We must be aware that, Spring delegates event handling to a TaskExecutor which may be asynchronous. Spring does not guarantee that the event is processed before the object is effectively converted. This method is discouraged in the case your TaskExecutor is asynchronous as the ID may be generated after the object has been converted, leading to an Exception:

我们必须注意,Spring将事件处理委托给一个TaskExecutor,它可能是异步的。Spring不保证在对象被有效转换之前处理该事件。在你的TaskExecutor是异步的情况下,不建议使用这种方法,因为ID可能在对象被转换之后生成,从而导致异常的发生。

InvalidDataAccessApiUsageException: Cannot autogenerate id of type java.util.UUID for entity

InvalidDataAccessApiUsageException。无法为实体自动生成 java.util.UUID 类型的 ID

We can register the event listener in the ApplicationContext by annotating it with @Component or by generating it in a @Configuration class:

我们可以在ApplicationContext中注册事件监听器,方法是用@Component注解它,或者在@Configuration类中生成它。

@Bean
public UuidIdentifiedEntityEventListener uuidIdentifiedEntityEventListener() {
    return new UuidIdentifiedEntityEventListener();
}

5. Using Entity Callbacks

5.使用实体回调

Spring infrastructure provides hooks to execute custom code at some points in the lifecycle of an entity. Those are called EntityCallbacks, and we can use them in our case to generate a UUID before the object is persisted in the database.

Spring基础架构提供了钩子,以便在实体生命周期的某些点上执行自定义代码。这些被称为EntityCallbacks,,在我们的案例中,我们可以使用它们在对象被持久化到数据库之前生成一个UUID。

Unlike the event listener method seen previously, callbacks guarantee that their execution is synchronous and the code will run at the expected point in the object’s lifecycle.

与之前看到的事件监听器方法不同,回调保证其执行是同步的,代码将在对象生命周期的预期点上运行。

Spring Data MongoDB provides a set of callbacks we can use in our application. In our case, we’ll use the same event as previously. Callbacks can be provided directly in the @Configuration class:

Spring Data MongoDB提供了一组我们可以在应用中使用的回调。在我们的案例中,我们将使用与之前相同的事件。回调可以直接在@Configuration类中提供

@Bean
public BeforeConvertCallback<UuidIdentifiedEntity> beforeSaveCallback() {
        
    return (entity, collection) -> {
          
        if (entity.getId() == null) {
            entity.setId(UUID.randomUUID());
        }
        return entity;
    };
}

We can also use a Component that implements the BeforeConvertCallback interface.

我们还可以使用一个实现BeforeConvertCallback接口的Component

6. Using Custom Repositories

6.使用自定义存储库

Spring Data MongoDB provides a third method to achieve our goal: using a custom repository implementation. Usually, we just have to declare an interface inheriting from MongoRepository, and then Spring handles repositories-related code.

Spring Data MongoDB提供了第三种方法来实现我们的目标:使用自定义的存储库实现。通常情况下,我们只需声明一个继承自MongoRepository的接口,然后由Spring处理与存储库相关的代码。

If we want to change the way Spring Data handles our objects, we can define custom code that Spring will execute at the repository level. To do so, we must first define an interface extending MongoRepository:

如果我们想改变Spring Data处理对象的方式,我们可以定义Spring将在存储库级别执行的自定义代码。要做到这一点,我们必须首先定义一个扩展MongoRepository的接口。

@NoRepositoryBean
public interface CustomMongoRepository<T extends UuidIdentifiedEntity> extends MongoRepository<T, UUID> { }

The @NoRepositoryBean annotation prevents Spring from generating the usual piece of code associated with a MongoRepository. This interface forces the use of UUID as the type of the ID in the objects.

@NoRepositoryBean 注释阻止 Spring 生成与 MongoRepository 相关的常规代码。该接口强制使用 UUID 作为对象中的 ID 类型。

Then, we must create a repository class that will define the required behavior to handle our UUIDs:

然后,我们必须创建一个资源库类,它将定义所需的行为来处理我们的UUID。

public class CustomMongoRepositoryImpl<T extends UuidIdentifiedEntity> 
  extends SimpleMongoRepository<T, UUID> implements CustomMongoRepository<T>

In this repository, we’ll have to catch all methods calls where we need to generate an ID by overriding the relevant methods of SimpleMongoRepository. Those methods are save() and insert() in our case:

在这个存储库中,我们必须通过覆盖SimpleMongoRepository的相关方法来捕捉所有需要生成ID的方法调用。在我们的例子中,这些方法是save()insert()

@Override
public <S extends T> S save(S entity) {
    generateId(entity);
    return super.save(entity);
}

Finally, we need to tell Spring to use our custom class as the implementation of the repositories instead of the default implementation. We do that in the @Configuration class:

最后,我们需要告诉Spring使用我们的自定义类作为存储库的实现,而不是默认的实现。我们在@Configuration类中这样做。

@EnableMongoRepositories(basePackages = "com.baeldung.repository", repositoryBaseClass = CustomMongoRepositoryImpl.class)

Then, we can declare our repositories as usual with no changes:

然后,我们可以像往常一样声明我们的存储库,不做任何改动。

public interface BookRepository extends MongoRepository<Book, UUID> { }

7. Conclusion

7.结语

In this article, we have seen three ways to implement UUIDs as MongoDB object’s ID with Spring Data MongoDB.

在这篇文章中,我们看到了用Spring Data MongoDB实现UUID作为MongoDB对象ID的三种方法。

As always, the code used in this article is available over on GitHub.

一如既往,本文中使用的代码可在GitHub上获得