当前位置: 首页 > news >正文

Spring Boot 与 MongoDB 集成最佳实践:构建灵活的数据存储系统

Spring Boot 与 MongoDB 集成最佳实践:构建灵活的数据存储系统

引言

MongoDB 是一个流行的 NoSQL 文档数据库,以其灵活的数据模型和水平扩展能力而闻名。本文将详细介绍如何在 Spring Boot 项目中集成 MongoDB,包括环境配置、实体映射、CRUD 操作、聚合查询、事务处理等核心功能。

一、环境配置

1.1 Maven 依赖

<dependencies> <!-- Spring Boot Data MongoDB Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <!-- Lombok (Optional) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- Validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>

1.2 配置文件

# application.yml spring: data: mongodb: uri: mongodb://localhost:27017/mydatabase username: admin password: password database: mydatabase auto-index-creation: true

1.3 多数据源配置

spring: data: mongodb: primary: uri: mongodb://localhost:27017/primarydb secondary: uri: mongodb://localhost:27017/secondarydb

二、实体映射

2.1 基础实体

@Document(collection = "users") @Data @NoArgsConstructor @AllArgsConstructor public class User { @Id private String id; @Indexed(unique = true) @Field("username") private String username; @Indexed(unique = true) @Field("email") private String email; @Field("password_hash") private String passwordHash; @Field("created_at") private LocalDateTime createdAt; @Field("updated_at") private LocalDateTime updatedAt; @Field("roles") private List<String> roles = new ArrayList<>(); @PrePersist public void prePersist() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } @PreUpdate public void preUpdate() { updatedAt = LocalDateTime.now(); } }

2.2 嵌套文档

@Document(collection = "products") @Data public class Product { @Id private String id; @Field("name") private String name; @Field("description") private String description; @Field("price") private BigDecimal price; @Field("category") private Category category; @Field("tags") private List<String> tags; @Field("stock") private Stock stock; @Data public static class Category { private String id; private String name; private String parentId; } @Data public static class Stock { private int quantity; private String warehouseId; private LocalDateTime lastUpdated; } }

2.3 索引配置

@Component public class MongoIndexConfig { @Autowired private MongoTemplate mongoTemplate; @PostConstruct public void initIndexes() { // 用户集合索引 mongoTemplate.indexOps(User.class) .ensureIndex(new Index("email", Sort.Direction.ASC).unique()); // 产品集合复合索引 mongoTemplate.indexOps(Product.class) .ensureIndex(new Index("category.id", Sort.Direction.ASC) .on("price", Sort.Direction.DESC)); // 文本索引 mongoTemplate.indexOps(Product.class) .ensureIndex(new TextIndexDefinition.TextIndexDefinitionBuilder() .onField("name") .onField("description") .build()); } }

三、Repository 接口

3.1 基础 Repository

public interface UserRepository extends MongoRepository<User, String> { Optional<User> findByUsername(String username); Optional<User> findByEmail(String email); List<User> findByRolesContaining(String role); List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end); @Query("{ 'email' : { $regex: ?0 } }") List<User> findByEmailRegex(String pattern); }

3.2 自定义 Repository 实现

public interface UserRepositoryCustom { List<User> findUsersWithPagination(int page, int size); List<User> searchUsers(String keyword); } public class UserRepositoryImpl implements UserRepositoryCustom { @Autowired private MongoTemplate mongoTemplate; @Override public List<User> findUsersWithPagination(int page, int size) { return mongoTemplate.find(Query.query(Criteria.where("roles").in("USER")) .skip((long) page * size) .limit(size) .with(Sort.by(Sort.Direction.DESC, "createdAt")), User.class); } @Override public List<User> searchUsers(String keyword) { TextQuery query = TextQuery.queryText(keyword) .sortByScore() .includeScore(); return mongoTemplate.find(query, User.class); } }

四、CRUD 操作

4.1 创建文档

@Service public class UserService { @Autowired private UserRepository userRepository; public User createUser(UserCreateRequest request) { User user = new User(); user.setUsername(request.getUsername()); user.setEmail(request.getEmail()); user.setPasswordHash(BCrypt.hashpw(request.getPassword(), BCrypt.gensalt())); user.setRoles(Arrays.asList("USER")); return userRepository.save(user); } public User createUserWithSession() { Session session = mongoTemplate.getMongoDatabase().startSession(); try { session.startTransaction(); User user = new User(); user.setUsername("test"); user.setEmail("test@example.com"); mongoTemplate.insert(user, session); // 创建关联数据 UserProfile profile = new UserProfile(); profile.setUserId(user.getId()); mongoTemplate.insert(profile, session); session.commitTransaction(); return user; } catch (Exception e) { session.abortTransaction(); throw e; } finally { session.close(); } } }

4.2 查询文档

public User getUserById(String id) { return userRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("User not found")); } public List<User> getUsersByRole(String role) { return userRepository.findByRolesContaining(role); } public Page<User> getUsersPage(int page, int size) { return userRepository.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"))); } public User getUserByEmail(String email) { return userRepository.findByEmail(email) .orElse(null); }

4.3 更新文档

public User updateUser(String id, UserUpdateRequest request) { User user = getUserById(id); if (request.getUsername() != null) { user.setUsername(request.getUsername()); } if (request.getEmail() != null) { user.setEmail(request.getEmail()); } return userRepository.save(user); } public void updateUserEmail(String userId, String newEmail) { Query query = Query.query(Criteria.where("_id").is(userId)); Update update = new Update() .set("email", newEmail) .set("updated_at", LocalDateTime.now()); mongoTemplate.updateFirst(query, update, User.class); } public void incrementUserLoginCount(String userId) { Query query = Query.query(Criteria.where("_id").is(userId)); Update update = new Update().inc("login_count", 1); mongoTemplate.updateFirst(query, update, User.class); }

4.4 删除文档

public void deleteUser(String id) { userRepository.deleteById(id); } public void deleteUsersByRole(String role) { Query query = Query.query(Criteria.where("roles").in(role)); mongoTemplate.remove(query, User.class); }

五、高级查询

5.1 聚合查询

public List<UserStatsDTO> getUserStatsByRole() { Aggregation aggregation = Aggregation.newAggregation( Aggregation.unwind("roles"), Aggregation.group("roles") .count().as("userCount") .avg("age").as("avgAge"), Aggregation.sort(Sort.Direction.DESC, "userCount") ); AggregationResults<UserStatsDTO> results = mongoTemplate.aggregate( aggregation, "users", UserStatsDTO.class); return results.getMappedResults(); } public List<Product> getProductsWithLowStock(int threshold) { Query query = Query.query(Criteria.where("stock.quantity").lt(threshold)); return mongoTemplate.find(query, Product.class); }

5.2 地理空间查询

public List<Store> findNearbyStores(double latitude, double longitude, double maxDistance) { Query query = Query.query(Criteria.where("location") .nearSphere(new Point(longitude, latitude)) .maxDistance(maxDistance / 6378137)); // 转换为弧度 return mongoTemplate.find(query, Store.class); }

5.3 文本搜索

public List<Product> searchProducts(String keyword) { TextQuery query = TextQuery.queryText(keyword) .sortByScore() .includeScore(); List<Product> products = mongoTemplate.find(query, Product.class); // 设置匹配分数 products.forEach(p -> { Double score = (Double) mongoTemplate.getCollection("products") .find(query.getQueryObject()) .first() .get("score"); // 处理分数 }); return products; }

六、事务处理

6.1 配置事务管理器

@Configuration @EnableMongoTransactionManagement public class MongoTransactionConfig { @Bean public MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) { return new MongoTransactionManager(dbFactory); } }

6.2 使用事务

@Service public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private InventoryRepository inventoryRepository; @Transactional public Order createOrder(OrderCreateRequest request) { // 创建订单 Order order = new Order(); order.setUserId(request.getUserId()); order.setItems(request.getItems()); order.setTotalAmount(request.getTotalAmount()); order = orderRepository.save(order); // 扣减库存 for (OrderItem item : request.getItems()) { Query query = Query.query(Criteria.where("productId").is(item.getProductId())); Update update = new Update().inc("quantity", -item.getQuantity()); mongoTemplate.updateFirst(query, update, Inventory.class); } return order; } }

七、批量操作

7.1 批量插入

public void batchInsertUsers(List<User> users) { mongoTemplate.insertAll(users); } public void batchInsertWithSession(List<User> users) { Session session = mongoTemplate.getMongoDatabase().startSession(); try { session.startTransaction(); MongoCollection<Document> collection = mongoTemplate.getCollection("users"); List<Document> documents = users.stream() .map(user -> { Document doc = new Document(); doc.append("username", user.getUsername()); doc.append("email", user.getEmail()); doc.append("created_at", user.getCreatedAt()); return doc; }) .collect(Collectors.toList()); collection.insertMany(documents); session.commitTransaction(); } catch (Exception e) { session.abortTransaction(); throw e; } finally { session.close(); } }

7.2 批量更新

public void batchUpdateUserStatus(List<String> userIds, String status) { Query query = Query.query(Criteria.where("_id").in(userIds)); Update update = new Update().set("status", status); mongoTemplate.updateMulti(query, update, User.class); }

八、性能优化

8.1 索引优化

@Component public class IndexOptimizer { @Autowired private MongoTemplate mongoTemplate; public void analyzeQueryPerformance(Query query, Class<?> entityClass) { ExplainCursor<Document> explain = mongoTemplate.getCollection( mongoTemplate.getCollectionName(entityClass)) .explain(query.getQueryObject()); Document executionStats = (Document) explain.first().get("executionStats"); System.out.println("Execution Time: " + executionStats.get("executionTimeMillis")); System.out.println("Total Docs Examined: " + executionStats.get("totalDocsExamined")); System.out.println("Total Keys Examined: " + executionStats.get("totalKeysExamined")); } }

8.2 读写分离

@Configuration public class MongoReplicaConfig { @Bean @Primary public MongoTemplate primaryMongoTemplate(MongoClient mongoClient) { return new MongoTemplate(mongoClient, "primarydb"); } @Bean public MongoTemplate secondaryMongoTemplate(MongoClient mongoClient) { MongoDatabase db = mongoClient.getDatabase("secondarydb"); db.withReadPreference(ReadPreference.secondaryPreferred()); return new MongoTemplate(mongoClient, "secondarydb"); } }

8.3 数据分片

@Configuration public class MongoShardingConfig { @Bean public MongoClient mongoClient() { ConnectionString connectionString = new ConnectionString( "mongodb://mongos1:27017,mongos2:27017/mydatabase"); MongoClientSettings settings = MongoClientSettings.builder() .applyConnectionString(connectionString) .build(); return MongoClients.create(settings); } }

九、监控与管理

9.1 健康检查

@Component public class MongoHealthIndicator implements HealthIndicator { @Autowired private MongoTemplate mongoTemplate; @Override public Health health() { try { mongoTemplate.getDb().runCommand(new Document("ping", 1)); return Health.up().build(); } catch (Exception e) { return Health.down(e).build(); } } }

9.2 指标监控

@Component public class MongoMetrics { private final Counter queriesCounter; private final Counter insertsCounter; private final Counter updatesCounter; private final Counter deletesCounter; public MongoMetrics(MeterRegistry meterRegistry) { this.queriesCounter = Counter.builder("mongodb.queries") .register(meterRegistry); this.insertsCounter = Counter.builder("mongodb.inserts") .register(meterRegistry); this.updatesCounter = Counter.builder("mongodb.updates") .register(meterRegistry); this.deletesCounter = Counter.builder("mongodb.deletes") .register(meterRegistry); } public void recordQuery() { queriesCounter.increment(); } public void recordInsert() { insertsCounter.increment(); } public void recordUpdate() { updatesCounter.increment(); } public void recordDelete() { deletesCounter.increment(); } }

十、最佳实践

10.1 集合命名规范

使用小写字母和下划线 复数形式 例如: users, products, orders

10.2 字段命名规范

使用小写字母和下划线 避免使用保留字 例如: user_id, created_at, is_active

10.3 索引策略

// 复合索引 mongoTemplate.indexOps("orders") .ensureIndex(new Index("user_id", Sort.Direction.ASC) .on("created_at", Sort.Direction.DESC)); // 唯一索引 mongoTemplate.indexOps("users") .ensureIndex(new Index("email", Sort.Direction.ASC).unique()); // TTL 索引(自动过期) mongoTemplate.indexOps("sessions") .ensureIndex(new Index("expire_at", Sort.Direction.ASC) .expire(0, TimeUnit.SECONDS));

10.4 数据模型设计

// 嵌入式文档(一对一关系) @Field("address") private Address address; // 引用文档(一对多关系) @Field("order_ids") private List<String> orderIds; // 数组字段(多值属性) @Field("tags") private List<String> tags;

十一、总结

MongoDB 为 Spring Boot 应用提供了灵活的数据存储解决方案。通过合理配置和使用,可以构建高性能、可扩展的数据库系统。在实际应用中,需要注意以下几点:

  1. 索引优化: 创建适当的索引提高查询性能
  2. 数据模型设计: 根据业务需求选择合适的数据结构
  3. 事务处理: 使用事务保证数据一致性
  4. 监控告警: 及时发现和处理问题

希望本文能帮助你在 Spring Boot 项目中成功集成 MongoDB!

http://www.jsqmd.com/news/798734/

相关文章:

  • [实例] SPI接口的ADC芯片全通道纯硬件驱动——基于HAL库和TL2518芯片
  • 2026 郑州 GEO 服务商选型指南 五强实力横评与避坑全攻略 - GEO优化
  • 英雄联盟专业视频编辑器:用League Director制作电影级游戏录像的完整指南
  • 微动感知雷达生命体征检测信号处理【附代码】
  • AIGC检测为什么改稿没用?算法看的不是单词是底层指标,怎么应对?
  • NVIDIA显卡终极调校指南:用Profile Inspector释放游戏潜能的简单方法
  • 【无人机编队控制5】多无人机分布式系统,协同路径规划与避碰,使用改进APF(人工势场法)。附MATLAB代码
  • 通信信号处理矢量处理器VLIW架构设计【附程序】
  • Unlock Music:3种创新用法让你重新掌控被加密的音乐收藏
  • Java原子累加器深度解析(一)
  • 2026 东莞 GEO 服务商优选指南 五强交付力横评与新手避坑手册 - GEO优化
  • 专业级Windows游戏控制器模拟终极方案:ViGEmBus深度解析与实战指南
  • 从安装到实战:用 Wireshark 抓取第一个 HTTP 请求,揭秘浏览器与服务器对话全过程
  • 3步搭建你的英雄联盟智能助手:LeagueAkari完整操作指南
  • 宽带矢量信号MQAM同步分析算法【附代码】
  • 深入STM32F429 LTDC双图层与DMA2D:打造流畅UI界面的性能优化指南
  • 2026 青岛 GEO 服务商怎么选?五强实力测评与选型避坑全指南 - GEO优化
  • QTableView拖拽进阶:如何优雅地实现整行/整列交换与移动(附GitHub源码)
  • SAP-ABAP:SAP 经典事务码使用指南(五篇连载) 第一篇:SE38 ABAP程序编辑事务码全解析
  • 2026 南京 GEO 服务商选型指南 五强交付能力横评与避坑实战 - GEO优化
  • 深入解析OCP协议:IP核通信的标准化语言与SoC设计实践
  • 自适应灰狼算法锂电池SOC与SOH估计【附代码】
  • 从IO充放电到AD采样:湿敏电阻CM-R/HR202低成本替代方案实战解析
  • 哪些海外国家最可能落地矿鸿/OpenHarmony矿山方案?1. 资源型发展中国家(最优先)
  • SteamAutoCrack:3步自动化破解Steam游戏的终极解决方案
  • Qt QML 模块化进阶:qmldir 实战避坑与高效配置
  • 大模型的 Token 是什么?输入 Token 和输出 Token 在计费上有什么区别?
  • 5-11午夜盘思
  • DFI 3.1规范解析:LPDDR3接口与移动内存低功耗设计
  • TINA-TI仿真实战:从运放振铃到电源设计的电路调试指南