UUID作为MongoDB中的实体ID

2023/05/18

1. 概述

默认情况下,MongoDB Java驱动程序生成ObjectId类型的ID。有时,我们可能希望使用另一种类型的数据作为对象的唯一标识符,例如UUID。但是,MongoDB Java驱动程序无法自动生成UUID

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

2. 共同点

应用程序只管理一种类型的数据是非常罕见的。为了简化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
}

对于本教程中的示例,我们将假设MongoDB数据库中持久保存的所有类都继承自此类。

3. 配置UUID支持

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

我们所要做的就是在启动时在我们的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);
}

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

spring.data.mongodb.uuid-representation=standard

4. 使用生命周期事件

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

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());
        }
    }
}

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

键入我们的事件以捕获UuidIdentifiedEntity类允许处理此抽象超类型的所有子类。一旦使用UUID作为ID的对象被转换,Spring将调用我们的代码。

我们必须意识到,Spring将事件处理委托给可能是异步的TaskExecutorSpring不保证在对象被有效转换之前处理完事件。如果你的TaskExecutor是异步的,则不鼓励使用此方法,因为ID可能会在对象转换后生成,从而导致异常:

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

我们可以通过使用@Component注解或在@Configuration类中生成它来在ApplicationContext中注册事件监听器:

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

5. 使用实体回调

Spring基础设施提供了在实体生命周期中的某些点执行自定义代码的钩子。这些称为EntityCallbacks,我们可以在我们的案例中使用它们在对象保存在数据库中之前生成UUID。

与前面看到的事件监听器方法不同,回调保证它们的执行是同步的,并且代码将在对象生命周期的预期时间点运行。

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

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

我们还可以使用实现BeforeConvertCallback接口的组件。

6. 使用自定义Repository

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

如果我们想改变Spring Data处理对象的方式,我们可以定义Spring将在Repository级别执行的自定义代码。为此,我们必须首先定义一个扩展MongoRepository的接口

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

@NoRepositoryBean注解阻止了Spring生成与MongoRepository关联的常规代码片段。此接口强制使用UUID作为对象中ID的类型。

然后,我们必须创建一个Repository类,它将定义处理我们的UUID所需的行为:

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

在此Repository中,我们必须通过覆盖SimpleMongoRepository的相关方法来捕获我们需要生成ID的所有方法调用。在我们的例子中,这些方法是save()和insert():

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

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

@EnableMongoRepositories(basePackages = "cn.tuyucheng.taketoday.repository", repositoryBaseClass = CustomMongoRepositoryImpl.class)

然后,我们可以像往常一样声明我们的Repository,无需其它更改:

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

7. 总结

在本文中,我们看到了三种使用Spring Data MongoDB将UUID实现为MongoDB对象ID的方法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章