SpringBoot整合MongoDB全流程实践指南

环境准备与基础集成

1.1 创建Spring Boot项目

使用Spring Initializr创建项目时,勾选以下依赖:

  • Spring Data MongoDB
  • Lombok(推荐)
  • Spring Web(可选)
curl https://start.spring.io/starter.zip -d dependencies=web,data-mongodb,lombok -d type=gradle-project -o mongo-demo.zip

1.2 配置连接参数

在application.yml中配置集群连接:

spring:
  data:
    mongodb:
      uri: mongodb+srv://<username>:<password>@cluster0.abcd.mongodb.net/<database>?retryWrites=true&w=majority
      auto-index-creation: true

生产环境建议使用加密配置:

@Configuration
public class MongoConfig {

    @Value("${encrypted.mongo.uri}")
    private String encryptedUri;

    @Bean
    public MongoClient mongoClient() {
        String decryptedUri = DecryptUtil.decrypt(encryptedUri);
        return MongoClients.create(decryptedUri);
    }
}

数据建模与Repository

2.1 实体类注解深度解析

@Document(collection = "products", collation = "en_US")
@CompoundIndex(def = "{'name': 1, 'category': -1}", name = "name_category_idx")
public class Product {
    @Id
    private String id;
    
    @Indexed(unique = true, direction = IndexDirection.DESCENDING)
    private String sku;
    
    @Field("product_name")
    @TextIndexed(weight = 2)
    private String name;
    
    @Transient
    private BigDecimal temporaryPrice;
    
    @CreatedDate
    private Instant createdAt;
    
    @Version
    private Long version;
}

2.2 自定义Repository实现

扩展基础Repository接口:

public interface CustomProductRepository {
    List<Product> findRecentlyUpdated(int days);
}

public class ProductRepositoryImpl implements CustomProductRepository {

    private final MongoTemplate mongoTemplate;

    public List<Product> findRecentlyUpdated(int days) {
        Criteria criteria = Criteria.where("lastModifiedDate")
                                   .gt(LocalDateTime.now().minusDays(days));
        Query query = new Query(criteria);
        return mongoTemplate.find(query, Product.class);
    }
}

复杂查询与聚合操作

3.1 多条件动态查询

使用QueryDSL实现类型安全的查询:

public interface ProductRepository extends 
    MongoRepository<Product, String>, QuerydslPredicateExecutor<Product> {}

// 使用示例
BooleanBuilder predicate = new BooleanBuilder();
QProduct product = QProduct.product;

if (StringUtils.hasText(name)) {
    predicate.and(product.name.containsIgnoreCase(name));
}
if (minPrice != null) {
    predicate.and(product.price.goe(minPrice));
}

Iterable<Product> result = repository.findAll(predicate);

3.2 聚合管道实战

统计各分类商品数量及平均价格:

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.group("category")
        .count().as("totalProducts")
        .avg("price").as("avgPrice"),
    Aggregation.sort(Sort.Direction.DESC, "avgPrice"),
    Aggregation.limit(10)
);

AggregationResults<CategoryStats> results = mongoTemplate.aggregate(
    aggregation, "products", CategoryStats.class);

事务管理与性能优化

4.1 多文档事务处理

@Transactional
public void transferInventory(String fromSku, String toSku, int quantity) {
    Product source = productRepo.findBySku(fromSku)
        .orElseThrow(() -> new ProductNotFoundException(fromSku));
    
    Product target = productRepo.findBySku(toSku)
        .orElseThrow(() -> new ProductNotFoundException(toSku));

    if (source.getStock() < quantity) {
        throw new InsufficientStockException();
    }

    source.setStock(source.getStock() - quantity);
    target.setStock(target.getStock() + quantity);

    productRepo.saveAll(List.of(source, target));
}

4.2 性能优化策略

  1. 索引优化工具使用:
mongoTemplate.indexOps(Product.class).getIndexInfo()
    .forEach(index -> log.info("Index details: {}", index));
  1. 批量写入优化:
BulkOperations bulkOps = mongoTemplate.bulkOps(BulkMode.ORDERED, Product.class);
products.forEach(product -> 
    bulkOps.insert(documentFactory.createBulkItem(product)));
BulkWriteResult result = bulkOps.execute();

监控与调试

5.1 集成Micrometer监控

配置指标收集:

management:
  metrics:
    enable:
      mongodb: true
  endpoints:
    web:
      exposure:
        include: health,metrics,mongodb

自定义指标采集:

@Bean
MongoMetricsCommandListener mongoMetricsCommandListener(MeterRegistry registry) {
    return new MongoMetricsCommandListener(registry);
}

5.2 查询性能分析

启用慢查询日志:

@Bean
public MongoClientSettings mongoClientSettings() {
    return MongoClientSettings.builder()
        .applyToClusterSettings(builder -> 
            builder.serverSelectionTimeout(5, TimeUnit.SECONDS))
        .applyToConnectionPoolSettings(builder ->
            builder.maxWaitTime(2, TimeUnit.SECONDS))
        .addCommandListener(new SlowQueryListener(100))
        .build();
}

测试策略

6.1 集成测试配置

使用Testcontainers进行真实数据库测试:

@Testcontainers
@DataMongoTest
@Import(TestConfig.class)
class ProductRepositoryTest {

    @Container
    static MongoDBContainer mongoDB = new MongoDBContainer("mongo:5.0");

    @DynamicPropertySource
    static void setProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.mongodb.uri", mongoDB::getReplicaSetUrl);
    }

    @Test
    void shouldSaveAndRetrieveProduct() {
        Product product = new Product("Test SKU", "Test Product");
        repository.save(product);
        assertThat(repository.findBySku("Test SKU")).isPresent();
    }
}

6.2 单元测试Mock技巧

使用Mockito进行Repository层测试:

@ExtendWith(MockitoExtension.class)
class ProductServiceTest {

    @Mock
    private ProductRepository repository;

    @InjectMocks
    private ProductService service;

    @Test
    void shouldHandleEmptyResult() {
        when(repository.findBySku(anyString())).thenReturn(Optional.empty());
        assertThrows(ProductNotFoundException.class, 
            () -> service.getProductDetails("INVALID_SKU"));
    }
}

高级特性集成

7.1 Change Stream监听

实现实时数据变更监听:

@Configuration
public class ChangeStreamConfig {

    @Bean
    public MessageListenerContainer messageListenerContainer(
        MongoTemplate mongoTemplate) {
        
        return new DefaultMessageListenerContainer(mongoTemplate) {
            @Override
            public void start() {
                super.start();
                ChangeStreamRequest<Product> request = ChangeStreamRequest.builder()
                    .collection("products")
                    .filter(newAggregation(match(where("operationType").in("insert", "update"))))
                    .publishFullDocumentOnly()
                    .build();
                
                this.register(request, Product.class, System.out::println);
            }
        };
    }
}

7.2 地理位置查询

实现附近门店搜索:

@Document
public class Store {
    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private GeoJsonPoint location;
}

public interface StoreRepository extends MongoRepository<Store, String> {
    
    @Query("{'location': {$near: {$geometry: {type: 'Point', coordinates: ?0 }, $maxDistance: ?1}}}")
    List<Store> findNearbyStores(double[] coordinates, double maxDistance);
}

生产环境最佳实践

8.1 连接池配置优化

spring:
  data:
    mongodb:
      uri: mongodb://host1,host2,host3/db?connectTimeoutMS=3000
      auto-index-creation: false # 生产环境应关闭
      client:
        min-pool-size: 5
        max-pool-size: 50
        max-idle-time: 180000

8.2 版本迁移策略

使用Flyway进行数据迁移:

@Configuration
public class MongoMigrationConfig {

    @Bean
    public FlywayMigrationStrategy flywayMigrationStrategy() {
        return flyway -> {
            flyway.setLocations("classpath:db/migration/mongo");
            flyway.migrate();
        };
    }
}

典型问题排查

9.1 连接超时问题分析

常见错误模式及解决方案:

  1. 防火墙限制:检查云安全组设置
  2. DNS解析失败:配置/etc/hosts或使用IP直连
  3. 驱动版本不兼容:确保Spring Data MongoDB版本与MongoDB服务器版本匹配

9.2 索引失效场景

典型索引失效案例:

// 错误:使用$or查询导致索引失效
@Query("{ $or: [ { 'name': ?0 }, { 'description': ?0 } ] }")
List<Product> searchProducts(String keyword);

// 正确:创建复合索引
@CompoundIndex(name = "search_idx", def = "{'name': 1, 'description': 1}")
正文到此结束
评论插件初始化中...
Loading...