UUID 作為 MongoDB 中的實體 ID
一、概述
默認情況下,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: 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
接口的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 將 UUID 實現為 MongoDB 對象 ID 的方法。
與往常一樣,本文中使用的代碼可 在 GitHub 上獲得。