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将事件处理委托给一个TaskExecutor
,这可能是异步的。Spring并不能保证事件在对象实际转换之前被处理。如果你的TaskExecutor
是异步的,这种方法不推荐,因为ID可能在对象转换后生成,导致异常:
InvalidDataAccessApiUsageException: 不能为类型java.util.UUID的实体自动生成ID
我们可以通过在ApplicationContext
中注解它为@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
接口的Component
。
6. 使用自定义仓库
Spring Data MongoDB提供了实现目标的第三种方法:使用自定义仓库实现。通常,我们只需要声明一个继承自MongoRepository
的接口,然后Spring会处理与仓库相关的代码。
如果我们想改变Spring Data处理我们对象的方式,我们可以定义自定义代码,Spring将在仓库级别执行。为此,**首先需要定义一个接口,它扩展MongoRepository
**:
@NoRepositoryBean
public interface CustomMongoRepository<T extends UuidIdentifiedEntity> extends MongoRepository<T, UUID> { }
@NoRepositoryBean
注解防止Spring为MongoRepository
生成通常关联的代码。这个接口强制使用UUID作为对象ID的类型。
然后,我们需要创建一个仓库类,定义处理我们UUID所需的行为:
public class CustomMongoRepositoryImpl<T extends UuidIdentifiedEntity>
extends SimpleMongoRepository<T, UUID> implements CustomMongoRepository<T>
在这个仓库中,我们必须重写SimpleMongoRepository
的相关方法来处理需要生成ID的情况,例如在我们的例子中是save()
和insert()
方法:
@Override
public <S extends T> S save(S entity) {
generateId(entity);
return super.save(entity);
}
最后,我们需要告诉Spring使用我们自定义的类作为仓库的实现,而不是默认实现。我们在@Configuration
类中做这件事:
@EnableMongoRepositories(basePackages = "com.baeldung.repository", repositoryBaseClass = CustomMongoRepositoryImpl.class)
然后,我们可以像往常一样声明仓库,无需任何更改:
public interface BookRepository extends MongoRepository<Book, UUID> { }
7. 总结
在这篇文章中,我们了解了三种使用Spring Data MongoDB实现MongoDB对象ID为UUID的方法。
如往常一样,本文中的代码可在GitHub上找到。