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

Spring Boot 整合 MongoDB:CRUD 与聚合查询实战

Spring Boot 整合 MongoDB:CRUD 与聚合查询实战指南

    • 一、环境配置与基础集成
      • 1.1 项目初始化与依赖配置
      • 1.2 配置文件设置
      • 1.3 实体类设计
    • 二、基础 CRUD 操作实现
      • 2.1 创建 Repository 接口
      • 2.2 服务层实现
      • 2.3 控制器层
    • 三、高级查询与聚合操作
      • 3.1 使用 MongoTemplate 实现复杂查询
      • 3.2 聚合查询实战
        • 3.2.1 基础聚合操作
        • 3.2.2 多阶段聚合管道
        • 3.2.3 联表查询 ($lookup)
    • 四、事务管理与性能优化
      • 4.1 MongoDB 事务支持
      • 4.2 索引优化策略
      • 4.3 批量操作优化
    • 五、测试与验证
      • 5.1 单元测试配置
      • 5.2 集成测试示例
    • 六、实战案例:电商用户行为分析系统
      • 6.1 数据模型设计
      • 6.2 核心分析功能实现
        • 6.2.1 用户行为漏斗分析
        • 6.2.2 热门商品分析
    • 七、性能监控与调优
      • 7.1 查询性能分析
      • 7.2 连接池配置优化
    • 八、安全最佳实践
      • 8.1 敏感数据加密
      • 8.2 审计功能实现
    • 九、总结与最佳实践
      • 9.1 核心经验总结
      • 9.2 推荐架构模式
      • 9.3 扩展阅读建议

一、环境配置与基础集成

1.1 项目初始化与依赖配置

首先创建一个新的 Spring Boot 项目,添加以下关键依赖到 pom.xml:

<dependencies><!-- Spring Data MongoDB --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!-- Lombok 简化代码 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- 测试支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

1.2 配置文件设置

在 application.yml 中配置 MongoDB 连接:

spring:data:mongodb:host: localhostport: 27017database: spring_mongoauthentication-database: admin  # 认证数据库username: adminpassword: admin123auto-index-creation: true  # 自动创建索引

1.3 实体类设计

创建基础实体类和带有 MongoDB 注解的领域模型:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Document(collection = "users")  // 指定集合名称
public class User {@Id                         // 主键标识private String id;@Indexed(unique = true)     // 唯一索引private String username;@Field("email_addr")        // 自定义字段名private String email;private String password;private Integer age;private List<String> roles;private Address address;    // 嵌套文档private Date createdAt;@Transient                  // 不持久化到数据库private String tempToken;
}@Data
@Builder
public class Address {private String country;private String city;private String street;private String zipCode;
}

二、基础 CRUD 操作实现

2.1 创建 Repository 接口

public interface UserRepository extends MongoRepository<User, String> {// 方法名自动推导查询List<User> findByUsername(String username);// 使用 @Query 注解自定义查询@Query("{ 'age' : { $gt: ?0, $lt: ?1 } }")List<User> findUsersByAgeBetween(int minAge, int maxAge);// 模糊查询List<User> findByUsernameLike(String regex);// 嵌套文档查询List<User> findByAddress_City(String city);
}

2.2 服务层实现

@Service
@RequiredArgsConstructor
public class UserService {private final UserRepository userRepository;// 创建用户public User createUser(User user) {user.setCreatedAt(new Date());return userRepository.save(user);}// 批量插入public List<User> batchCreate(List<User> users) {return userRepository.saveAll(users);}// 查询所有用户public List<User> findAllUsers() {return userRepository.findAll();}// 分页查询public Page<User> findUsersByPage(int page, int size) {return userRepository.findAll(PageRequest.of(page, size, Sort.by("createdAt").descending()));}// 更新用户public User updateUser(String id, User user) {user.setId(id);return userRepository.save(user);}// 部分更新public void partialUpdate(String id, String key, Object value) {Query query = new Query(Criteria.where("id").is(id));Update update = new Update().set(key, value);mongoTemplate.updateFirst(query, update, User.class);}// 删除用户public void deleteUser(String id) {userRepository.deleteById(id);}// 检查用户名是否存在public boolean existsByUsername(String username) {return userRepository.existsByUsername(username);}
}

2.3 控制器层

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {private final UserService userService;@PostMappingpublic ResponseEntity<User> create(@RequestBody User user) {if (userService.existsByUsername(user.getUsername())) {throw new RuntimeException("用户名已存在");}return ResponseEntity.ok(userService.createUser(user));}@GetMappingpublic ResponseEntity<List<User>> listAll() {return ResponseEntity.ok(userService.findAllUsers());}@GetMapping("/page")public ResponseEntity<Page<User>> listByPage(@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "10") int size) {return ResponseEntity.ok(userService.findUsersByPage(page, size));}@PutMapping("/{id}")public ResponseEntity<User> update(@PathVariable String id, @RequestBody User user) {return ResponseEntity.ok(userService.updateUser(id, user));}@DeleteMapping("/{id}")public ResponseEntity<Void> delete(@PathVariable String id) {userService.deleteUser(id);return ResponseEntity.noContent().build();}
}

三、高级查询与聚合操作

3.1 使用 MongoTemplate 实现复杂查询

@Service
@RequiredArgsConstructor
public class UserQueryService {private final MongoTemplate mongoTemplate;// 多条件动态查询public List<User> complexQuery(String username, Integer minAge, String city) {Criteria criteria = new Criteria();if (StringUtils.hasText(username)) {criteria.and("username").regex(username, "i");}if (minAge != null) {criteria.and("age").gte(minAge);}if (StringUtils.hasText(city)) {criteria.and("address.city").is(city);}Query query = new Query(criteria);return mongoTemplate.find(query, User.class);}// 字段投影public List<User> findUsersWithSelectedFields() {Query query = new Query();query.fields().include("username").include("email").include("address.city");return mongoTemplate.find(query, User.class);}// 排序与限制public List<User> findTop5OldestUsers() {Query query = new Query().with(Sort.by(Sort.Direction.DESC, "age")).limit(5);return mongoTemplate.find(query, User.class);}
}

3.2 聚合查询实战

3.2.1 基础聚合操作
public List<AgeGroup> groupUsersByAge() {Aggregation aggregation = Aggregation.newAggregation(Aggregation.group("age").count().as("count").addToSet("username").as("usernames"),Aggregation.sort(Sort.Direction.DESC, "count"));return mongoTemplate.aggregate(aggregation, "users", AgeGroup.class).getMappedResults();
}@Data
public static class AgeGroup {private Integer age;private Integer count;private List<String> usernames;
}
3.2.2 多阶段聚合管道
public List<CityStats> getCityStatistics() {Aggregation aggregation = Aggregation.newAggregation(// 按城市分组Aggregation.group("address.city").count().as("userCount").avg("age").as("averageAge").max("age").as("maxAge").min("age").as("minAge"),// 添加计算字段Aggregation.project().and("city").previousOperation().and("userCount").as("userCount").and("averageAge").as("averageAge").and("maxAge").as("maxAge").and("minAge").as("minAge").andExpression("userCount / [0]", totalUserCount()).as("percentage"),// 排序Aggregation.sort(Sort.Direction.DESC, "userCount"),// 限制结果Aggregation.limit(10));return mongoTemplate.aggregate(aggregation, "users", CityStats.class).getMappedResults();
}private long totalUserCount() {return mongoTemplate.count(new Query(), User.class);
}@Data
public static class CityStats {private String city;private Long userCount;private Double averageAge;private Integer maxAge;private Integer minAge;private Double percentage;
}
3.2.3 联表查询 ($lookup)
public List<UserWithOrders> getUsersWithOrders() {Aggregation aggregation = Aggregation.newAggregation(Aggregation.lookup("orders", "id", "userId", "orders"),Aggregation.project().and("id").as("userId").and("username").as("username").and("email").as("email").and("orders").as("orders").andExclude("_id"));return mongoTemplate.aggregate(aggregation, "users", UserWithOrders.class).getMappedResults();
}@Data
public static class UserWithOrders {private String userId;private String username;private String email;private List<Order> orders;
}@Data
public static class Order {private String orderId;private BigDecimal amount;private Date orderDate;
}

四、事务管理与性能优化

4.1 MongoDB 事务支持

@Service
@RequiredArgsConstructor
public class TransactionalService {private final MongoTemplate mongoTemplate;private final UserRepository userRepository;@Transactionalpublic void transferPoints(String fromUserId, String toUserId, int points) {// 检查用户是否存在User fromUser = userRepository.findById(fromUserId).orElseThrow(() -> new RuntimeException("转出用户不存在"));User toUser = userRepository.findById(toUserId).orElseThrow(() -> new RuntimeException("转入用户不存在"));// 检查余额if (fromUser.getPoints() < points) {throw new RuntimeException("积分不足");}// 更新转出用户Query fromQuery = new Query(Criteria.where("id").is(fromUserId));Update fromUpdate = new Update().inc("points", -points);mongoTemplate.updateFirst(fromQuery, fromUpdate, User.class);// 更新转入用户Query toQuery = new Query(Criteria.where("id").is(toUserId));Update toUpdate = new Update().inc("points", points);mongoTemplate.updateFirst(toQuery, toUpdate, User.class);// 记录交易日志TransactionLog log = TransactionLog.builder().fromUserId(fromUserId).toUserId(toUserId).points(points).createdAt(new Date()).build();mongoTemplate.insert(log);}
}

4.2 索引优化策略

@Configuration
public class MongoIndexConfig {@Autowiredprivate MongoTemplate mongoTemplate;@PostConstructpublic void initIndexes() {// 复合索引IndexOperations userIndexOps = mongoTemplate.indexOps(User.class);IndexDefinition compoundIndex = new Index().on("username", Sort.Direction.ASC).on("email", Sort.Direction.ASC).named("username_email_compound_index");userIndexOps.ensureIndex(compoundIndex);// TTL索引 (自动过期)IndexDefinition ttlIndex = new Index().on("createdAt", Sort.Direction.ASC).expire(30, TimeUnit.DAYS);userIndexOps.ensureIndex(ttlIndex);// 文本索引IndexDefinition textIndex = new Index().on("username", Sort.Direction.ASC).on("email", Sort.Direction.ASC).named("user_text_search").text();userIndexOps.ensureIndex(textIndex);}
}

4.3 批量操作优化

public void bulkInsertUsers(List<User> users) {BulkOperations bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED, User.class);for (User user : users) {bulkOps.insert(user);}bulkOps.execute();
}public void bulkUpdateUserPoints(Map<String, Integer> userIdToPointsMap) {BulkOperations bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, User.class);userIdToPointsMap.forEach((userId, points) -> {Query query = new Query(Criteria.where("id").is(userId));Update update = new Update().inc("points", points);bulkOps.updateOne(query, update);});BulkWriteResult result = bulkOps.execute();log.info("Updated {} documents", result.getModifiedCount());
}

五、测试与验证

5.1 单元测试配置

@DataMongoTest
@ExtendWith(SpringExtension.class)
public class UserRepositoryTest {@Autowiredprivate UserRepository userRepository;@Autowiredprivate MongoTemplate mongoTemplate;@BeforeEachvoid setup() {// 清空集合mongoTemplate.dropCollection(User.class);// 初始化测试数据User user1 = User.builder().username("user1").email("user1@test.com").age(25).address(Address.builder().city("北京").country("中国").build()).build();User user2 = User.builder().username("user2").email("user2@test.com").age(30).address(Address.builder().city("上海").country("中国").build()).build();userRepository.saveAll(List.of(user1, user2));}@Testvoid testFindByUsername() {Optional<User> user = userRepository.findByUsername("user1");assertTrue(user.isPresent());assertEquals("user1@test.com", user.get().getEmail());}@Testvoid testFindByAddressCity() {List<User> users = userRepository.findByAddress_City("北京");assertEquals(1, users.size());assertEquals("user1", users.get(0).getUsername());}
}

5.2 集成测试示例

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {@Autowiredprivate MockMvc mockMvc;@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate UserRepository userRepository;@Testvoid testCreateAndGetUser() throws Exception {User newUser = User.builder().username("testuser").email("test@example.com").age(28).build();// 创建用户mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(newUser))).andExpect(status().isOk()).andExpect(jsonPath("$.username").value("testuser"));// 查询用户mockMvc.perform(get("/api/users")).andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(greaterThanOrEqualTo(1))));}
}

六、实战案例:电商用户行为分析系统

6.1 数据模型设计

@Data
@Document(collection = "user_actions")
public class UserAction {@Idprivate String id;private String userId;private ActionType actionType; // VIEW, CLICK, ADD_TO_CART, PURCHASEprivate String productId;private String categoryId;private BigDecimal price;private Date actionTime;// 用户设备信息private String deviceType; // MOBILE, DESKTOP, TABLETprivate String os;private String browser;// 地理位置信息private String country;private String city;private String ipAddress;
}public enum ActionType {VIEW,CLICK,ADD_TO_CART,REMOVE_FROM_CART,PURCHASE,SEARCH,LOGIN,LOGOUT
}

6.2 核心分析功能实现

6.2.1 用户行为漏斗分析
public FunnelAnalysisResult analyzeUserFunnel(Date startDate, Date endDate) {Aggregation aggregation = Aggregation.newAggregation(Aggregation.match(Criteria.where("actionTime").gte(startDate).lte(endDate)),Aggregation.group("userId").push("actionType").as("actions"),Aggregation.project().and("_id").as("userId").and("actions").as("actions").and(ArrayOperators.Size.lengthOfArray("actions")).as("actionCount"),Aggregation.match(Criteria.where("actionCount").gte(3)),Aggregation.facet().and(Aggregation.match(Criteria.where("actions").all("VIEW", "ADD_TO_CART", "PURCHASE")),Aggregation.count().as("completeFunnelCount")).as("completeFunnel").and(Aggregation.match(Criteria.where("actions").all("VIEW", "ADD_TO_CART")),Aggregation.count().as("cartAbandonCount")).as("cartAbandon").and(Aggregation.match(Criteria.where("actions").is("VIEW")),Aggregation.count().as("viewOnlyCount")).as("viewOnly"),Aggregation.project().and("completeFunnel.completeFunnelCount").as("completeFunnelCount").and("cartAbandon.cartAbandonCount").as("cartAbandonCount").and("viewOnly.viewOnlyCount").as("viewOnlyCount").andExpression("completeFunnelCount / viewOnlyCount * 100").as("conversionRate"));return mongoTemplate.aggregate(aggregation, "user_actions", FunnelAnalysisResult.class).getUniqueMappedResult();
}@Data
public static class FunnelAnalysisResult {private int completeFunnelCount;private int cartAbandonCount;private int viewOnlyCount;private double conversionRate;
}
6.2.2 热门商品分析
public List<ProductAnalysis> analyzeTopProducts(int limit, Date startDate, Date endDate) {Aggregation aggregation = Aggregation.newAggregation(Aggregation.match(Criteria.where("actionTime").gte(startDate).lte(endDate).and("actionType").in("VIEW", "ADD_TO_CART", "PURCHASE")),Aggregation.group("productId").count().as("viewCount").sum(ConditionalOperators.when(Criteria.where("actionType").is("ADD_TO_CART")).then(1).otherwise(0)).as("cartAddCount").sum(ConditionalOperators.when(Criteria.where("actionType").is("PURCHASE")).then(1).otherwise(0)).as("purchaseCount").avg("price").as("avgPrice"),Aggregation.project().and("_id").as("productId").and("viewCount").as("viewCount").and("cartAddCount").as("cartAddCount").and("purchaseCount").as("purchaseCount").and("avgPrice").as("avgPrice").andExpression("purchaseCount / viewCount * 100").as("conversionRate"),Aggregation.sort(Sort.Direction.DESC, "purchaseCount"),Aggregation.limit(limit));return mongoTemplate.aggregate(aggregation, "user_actions", ProductAnalysis.class).getMappedResults();
}@Data
public static class ProductAnalysis {private String productId;private long viewCount;private long cartAddCount;private long purchaseCount;private double avgPrice;private double conversionRate;
}

七、性能监控与调优

7.1 查询性能分析

public void analyzeQueryPerformance() {// 启用查询分析mongoTemplate.setQueryMetaDataProvider(new QueryMetaDataProvider() {@Overridepublic Document getMetaData(MongoAction mongoAction) {return new Document("comment", "performance analysis");}});// 执行查询并获取分析结果Query query = new Query(Criteria.where("age").gt(25));query.withHint("age_1"); // 强制使用特定索引List<User> users = mongoTemplate.find(query, User.class);// 获取查询执行统计MongoDatabase db = mongoTemplate.getDb();Document profileResult = db.runCommand(new Document("profile", 2)); // 2=全量分析log.info("Query profile results: {}", profileResult.toJson());
}

7.2 连接池配置优化

spring:data:mongodb:host: localhostport: 27017database: spring_mongousername: adminpassword: admin123auto-index-creation: true# 连接池配置options:min-connections-per-host: 10max-connections-per-host: 100threads-allowed-to-block-for-connection-multiplier: 5max-wait-time: 120000connect-timeout: 10000socket-timeout: 60000server-selection-timeout: 30000max-connection-idle-time: 60000max-connection-life-time: 1800000

八、安全最佳实践

8.1 敏感数据加密

@Document(collection = "secure_users")
@Data
public class SecureUser {@Idprivate String id;private String username;@Encryptedprivate String creditCardNumber;@Encryptedprivate String ssn;// 其他非敏感字段private String address;private String phone;
}@Configuration
public class MongoEncryptionConfig {@Beanpublic EncryptionKeyResolver encryptionKeyResolver() {return new EncryptionKeyResolver() {@Overridepublic Map<String, byte[]> getEncryptionKeys() {// 从安全存储获取加密密钥Map<String, byte[]> keys = new HashMap<>();keys.put("creditCardKey", loadKeyFromVault("creditCardKey"));keys.put("ssnKey", loadKeyFromVault("ssnKey"));return keys;}};}private byte[] loadKeyFromVault(String keyId) {// 实现从安全存储获取密钥的逻辑return new byte[32]; // 示例返回32字节密钥}
}

8.2 审计功能实现

@Document
public abstract class AuditableEntity {@CreatedByprivate String createdBy;@CreatedDateprivate Date createdDate;@LastModifiedByprivate String lastModifiedBy;@LastModifiedDateprivate Date lastModifiedDate;
}@Configuration
@EnableMongoAuditing
public class MongoAuditConfig {@Beanpublic AuditorAware<String> auditorAware() {return () -> Optional.ofNullable(SecurityContextHolder.getContext()).map(SecurityContext::getAuthentication).map(Authentication::getName);}
}// 实体类继承AuditableEntity
@Document(collection = "products")
public class Product extends AuditableEntity {@Idprivate String id;private String name;private BigDecimal price;// 其他字段...
}

九、总结与最佳实践

9.1 核心经验总结

  1. 文档设计原则:
    • 根据查询模式设计文档结构
    • 合理使用嵌入和引用
    • 控制文档大小,避免超过16MB限制
  2. 性能优化要点:
    • 为常用查询创建适当索引
    • 使用投影减少网络传输
    • 批量操作替代单条操作
  3. 事务使用建议:
    • 仅在必要时使用多文档事务
    • 控制事务持续时间
    • 处理乐观锁冲突

9.2 推荐架构模式

客户端
API网关
用户服务
订单服务
商品服务
MongoDB用户库
MongoDB订单库
MongoDB商品库

9.3 扩展阅读建议

  1. MongoDB官方文档:
    • 聚合管道
    • 事务
    • 性能优化
  2. Spring Data MongoDB参考:
    • 自定义Repository
    • 查询DSL
    • 审计功能
http://www.xdnf.cn/news/18975.html

相关文章:

  • Jenkins 全方位指南:安装、配置、部署与实战应用(含图解)
  • 如何规划一年、三年、五年的IP发展路线图?
  • 01.<<基础入门:了解网络的基本概念>>
  • Leetcode 深度优先搜索 (15)
  • WINTRUST!_ExplodeMessag函数中的pCatAdd
  • Yolov8 pose 推理部署笔记
  • Vue开发避坑:箭头函数与普通函数的正确使用指南
  • LeetCode 刷题【55. 跳跃游戏】
  • 从协作机器人到智能协作机器人:工业革命的下一跳
  • 【JavaScript】递归的问题以及优化方法
  • 安宝特方案丨安宝特工业AR全链路解决方案
  • Unity游戏打包——iOS打包基础、上传
  • java后端的各种注解
  • Linux 禁止 su 的几种限制手段:从 NoNewPrivileges 到 PAM 配置
  • GitHub 宕机自救指南:确保开发工作不间断
  • 大数据毕业设计选题推荐-基于大数据的存量房网上签约月统计信息可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • 学习嵌入式之驱动——I2C子系统
  • 深度学习篇---VGGNet
  • 一个基于物理信息神经网络(Physics-Informed Neural Network, PINN)的多变量时间序列预测模型MATLAB代码
  • Windows 7-11通用,这工具让电脑提速300%
  • 2025.8.28总结
  • HTTP 范围请求:为什么你的下载可以“断点续传”?
  • Chrome 插件开发实战:从入门到精通
  • vue2使用el-form动态参数展示并非空校验
  • 数据结构青铜到王者第九话---二叉树(2)
  • 自下而上的树形dp
  • 深度学习——卷积神经网络(PyTorch 实现 MNIST 手写数字识别案例)
  • pcl_案例2 叶片与根茎的分离
  • 机器视觉学习-day09-图像矫正
  • Day30 多线程编程 同步与互斥 任务队列调度