MongoDB + Java 使用

MongoDB + Java 使用

目录

  1. 第一部分:基础配置
  2. 第二部分:MongoTemplate 基本操作
  3. 第三部分:聚合管道详解
  4. 第四部分:代码速查表
  5. 第五部分:实战案例
  6. 第六部分:性能优化

第一部分:基础配置

1. Maven 依赖

<!-- Spring Data MongoDB -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
    <version>2.7.0</version>
</dependency>

<!-- 仅用 MongoDB 驱动 -->
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.12.10</version>
</dependency>

2. 配置文件 (application.yml)

spring:
  data:
    mongodb:
      # 方式一:URI 连接
      uri: mongodb://username:password@localhost:27017/database_name
      
      # 方式二:分离配置
      host: localhost
      port: 27017
      database: database_name
      username: username
      password: password
      
      # 其他配置
      auto-index-creation: true  # 自动创建索引
      connection-timeout: 5000   # 连接超时
      
      # 连接池配置
      max-pool-size: 50
      min-pool-size: 10

3. Entity 类定义

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.Date;

@Document(collection = "messages")  // 指定集合名
public class Message {
    
    @Id
    private String id;  // MongoDB 的 _id
    
    @Field("fromUserId")  // 映射到 MongoDB 字段
    private String from;
    
    @Field("toGroupId")
    private String to;
    
    private String content;
    
    private Date createTime;
    
    private String type;  // 消息类型
    
    // Getters and Setters
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    
    public String getFrom() { return from; }
    public void setFrom(String from) { this.from = from; }
    
    public String getTo() { return to; }
    public void setTo(String to) { this.to = to; }
    
    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
    
    public Date getCreateTime() { return createTime; }
    public void setCreateTime(Date createTime) { this.createTime = createTime; }
    
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
}

第二部分:MongoTemplate 基本操作

1. 注入 MongoTemplate

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;

@Service
public class MessageService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    // 使用 mongoTemplate...
}

2. 基本的 CRUD 操作

插入操作

// 单条插入
Message message = new Message();
message.setFrom("user1");
message.setTo("group1");
message.setContent("Hello");
message.setCreateTime(new Date());

mongoTemplate.insert(message);  // 插入,如果 _id 存在会报错
// 或
mongoTemplate.save(message);    // 保存,_id 存在会更新

// 批量插入
List<Message> messages = new ArrayList<>();
mongoTemplate.insertAll(messages);

查询操作

// 查询单条
Message msg = mongoTemplate.findById("12345", Message.class);

// 查询所有
List<Message> all = mongoTemplate.findAll(Message.class);

// 条件查询
Query query = new Query();
query.addCriteria(Criteria.where("from").is("user1"));
List<Message> userMessages = mongoTemplate.find(query, Message.class);

更新操作

// 更新单个字段
Update update = new Update();
update.set("content", "Updated content");
mongoTemplate.updateFirst(query, update, Message.class);  // 更新第一条
mongoTemplate.updateMulti(query, update, Message.class);  // 更新全部

删除操作

// 条件删除
mongoTemplate.remove(query, Message.class);
// 删除整个集合
mongoTemplate.dropCollection(Message.class);

3. Query 和 Criteria 用法

import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Criteria;

// 等于
Query query = new Query(Criteria.where("from").is("user1"));

// 不等于
query = new Query(Criteria.where("from").ne("user1"));

// 大于、小于
query = new Query(Criteria.where("createTime").gt(startDate).lte(endDate));

// in 操作
query = new Query(Criteria.where("from").in("user1", "user2", "user3"));

// 正则匹配
query = new Query(Criteria.where("content").regex("^Hello"));

// 组合条件 (AND)
query = new Query()
    .addCriteria(Criteria.where("from").is("user1"))
    .addCriteria(Criteria.where("type").is("text"));

// 或条件 (OR)
query = new Query()
    .addCriteria(new Criteria().orOperator(
        Criteria.where("from").is("user1"),
        Criteria.where("from").is("user2")
    ));

// 分页和排序
query.with(new PageRequest(0, 10));  // 第 0 页,每页 10 条
query.with(Sort.by("createTime").descending());  // 按创建时间倒序

// 字段投影(只查询某些字段)
query.fields().include("from", "content").exclude("_id");

// 限制条数
query.limit(10);  // 只查询 10 条

第三部分:聚合管道详解

什么是聚合管道?

聚合管道是一种数据处理框架,类似于工厂流水线:

原始数据 
  ↓
[$match]    → 筛选数据
  ↓
[$group]    → 分组统计
  ↓
[$project]  → 字段投影
  ↓
[$sort]     → 排序
  ↓
结果数据

聚合管道的8个主要阶段

1. $match - 筛选阶段

作用:过滤文档,类似 SQL 的 WHERE

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.match(
        new Criteria().andOperator(
            Criteria.where("from").is("user1"),
            Criteria.where("type").is("text")
        )
    )
);

2. $group - 分组阶段

作用:分组并聚合数据,类似 SQL 的 GROUP BY

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.group("$from")  // 按 from 字段分组
        .count().as("count")
        .sum("$points").as("totalPoints")
        .avg("$length").as("avgLength")
        .min("$price").as("minPrice")
        .max("$price").as("maxPrice")
);

$group 支持的聚合操作

操作符 说明 例子
$sum 求和 .sum("$price").as("total")
$avg 平均值 .avg("$price").as("avgPrice")
$min 最小值 .min("$price").as("minPrice")
$max 最大值 .max("$price").as("maxPrice")
$first 第一个值 .first("$name").as("firstName")
$last 最后一个值 .last("$name").as("lastName")
$push 数组 .push("$value").as("values")
$addToSet 去重数组 .addToSet("$tag").as("tags")

3. $project - 投影阶段

作用:选择或创建字段,类似 SQL 的 SELECT

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.project()
        .include("from", "content")          // 包含字段
        .exclude("_id")                      // 排除字段
        .and("from").as("sender")            // 重命名
        .andExpression("price * 2").as("doublePrice")  // 表达式
);

4. $sort - 排序阶段

作用:排序结果

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.sort(Sort.by(Sort.Direction.DESC, "createTime"))
);

5. $limit 和 $skip - 分页阶段

作用:限制和跳过文档数

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.skip(10),    // 跳过 10 条
    Aggregation.limit(5)     // 取 5 条
);

6. $lookup - 左连接阶段

作用:关联另一个集合,类似 SQL 的 LEFT JOIN

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.lookup("users", "from", "_id", "sender"),
    Aggregation.unwind("$sender")
);

7. $unwind - 展开阶段

作用:将数组字段展开成多个文档

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.unwind("$tags")
);

8. $count - 计数阶段

作用:统计文档数量

Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.count().as("total")
);

完整聚合管道例子

import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;

public class MessageService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    // 例子 1:统计每个用户发送的消息数
    public List<Map> countMessageByUser() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("type").is("text")),
            Aggregation.group("$from")
                .count().as("messageCount"),
            Aggregation.sort(Sort.by(Sort.Direction.DESC, "messageCount")),
            Aggregation.limit(10)
        );
        
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, 
            "messages",
            Map.class
        );
        
        return results.getMappedResults();
    }
    
    // 例子 2:用户活动分析
    public List<UserAnalysis> analyzeUserActivity() {
        Aggregation aggregation = Aggregation.newAggregation(
            // 1. 筛选:最近 30 天
            Aggregation.match(
                Criteria.where("createTime").gte(
                    new Date(System.currentTimeMillis() - 30L * 24 * 60 * 60 * 1000)
                )
            ),
            
            // 2. 分组
            Aggregation.group("$from")
                .count().as("messageCount")
                .sum("$points").as("totalPoints")
                .addToSet("$to").as("groups"),
            
            // 3. 投影
            Aggregation.project()
                .and("_id").as("userId")
                .include("messageCount", "totalPoints")
                .andExpression("messageCount * 10 + totalPoints").as("activityScore"),
            
            // 4. 排序
            Aggregation.sort(Sort.by(Sort.Direction.DESC, "activityScore")),
            
            // 5. 分页
            Aggregation.limit(10)
        );
        
        return mongoTemplate.aggregate(
            aggregation,
            "messages",
            UserAnalysis.class
        ).getMappedResults();
    }
    
    // 例子 3:群组统计
    public List<GroupStat> getGroupStatistics() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(
                new Criteria().andOperator(
                    Criteria.where("createTime").gte(startDate),
                    Criteria.where("type").in("text", "image")
                )
            ),
            Aggregation.group("$to")
                .count().as("totalMessages")
                .sum("$points").as("totalPoints")
                .avg("$length").as("avgLength"),
            Aggregation.sort(Sort.by(Sort.Direction.DESC, "totalMessages"))
        );
        
        return mongoTemplate.aggregate(
            aggregation,
            "messages",
            GroupStat.class
        ).getMappedResults();
    }
}

第四部分:代码速查表

基础操作速查

注入 MongoTemplate

@Autowired
private MongoTemplate mongoTemplate;

CRUD 基础操作

// 插入
mongoTemplate.insert(message);
mongoTemplate.save(message);
mongoTemplate.insertAll(messages);

// 查询
mongoTemplate.findById("123", Message.class);
mongoTemplate.findAll(Message.class);
mongoTemplate.find(query, Message.class);
mongoTemplate.findOne(query, Message.class);

// 更新
mongoTemplate.updateFirst(query, update, Message.class);
mongoTemplate.updateMulti(query, update, Message.class);

// 删除
mongoTemplate.remove(query, Message.class);

Criteria 条件速查

// 基础条件
Criteria.where("from").is("user1")           // 等于
Criteria.where("from").ne("user1")           // 不等于
Criteria.where("age").gt(18).lt(100)         // 大小比较
Criteria.where("status").in("active", "pending")  // in
Criteria.where("name").regex("^A")           // 正则
Criteria.where("email").exists(true)         // 存在字段

// 组合条件
new Criteria().andOperator(...)              // AND
new Criteria().orOperator(...)               // OR

聚合操作速查

// 基础聚合
Aggregation agg = Aggregation.newAggregation(
    Aggregation.match(...),      // 筛选
    Aggregation.group(...),      // 分组
    Aggregation.project(...),    // 投影
    Aggregation.sort(...),       // 排序
    Aggregation.limit(...)       // 分页
);

// 执行聚合
AggregationResults<T> results = mongoTemplate.aggregate(agg, "collection", T.class);
List<T> list = results.getMappedResults();

常用代码片段

分页查询

public Page<Message> findPage(int page, int size) {
    Query query = new Query()
        .skip((long) page * size)
        .limit(size)
        .with(Sort.by("createTime").descending());
    
    long total = mongoTemplate.count(query, Message.class);
    List<Message> list = mongoTemplate.find(query, Message.class);
    
    return new PageImpl<>(list, PageRequest.of(page, size), total);
}

统计聚合

public Map<String, Integer> countByType() {
    Aggregation agg = Aggregation.newAggregation(
        Aggregation.group("$type").count().as("count")
    );
    
    AggregationResults<Map> results = mongoTemplate.aggregate(
        agg, "messages", Map.class
    );
    
    Map<String, Integer> map = new HashMap<>();
    for (Map doc : results.getMappedResults()) {
        map.put((String) doc.get("_id"), (Integer) doc.get("count"));
    }
    return map;
}

删除过期数据

public void deleteOldMessages(int daysToKeep) {
    Date expireDate = new Date(
        System.currentTimeMillis() - daysToKeep * 24 * 60 * 60 * 1000
    );
    
    Query query = new Query(Criteria.where("createTime").lt(expireDate));
    DeleteResult result = mongoTemplate.remove(query, Message.class);
    
    System.out.println("删除了 " + result.getDeletedCount() + " 条记录");
}

创建索引

@Configuration
public class MongoIndexConfig {
    
    @Bean
    public CommandLineRunner initIndexes(MongoTemplate mongoTemplate) {
        return args -> {
            Index indexFrom = new Index("from", Sort.Direction.ASC);
            mongoTemplate.indexOps(Message.class).ensureIndex(indexFrom);
            
            Index compoundIndex = new Index()
                .on("from", Sort.Direction.ASC)
                .on("createTime", Sort.Direction.DESC);
            mongoTemplate.indexOps(Message.class).ensureIndex(compoundIndex);
        };
    }
}

第五部分:实战案例

案例 1:Fiora 聊天消息统计

@Service
public class FioraMessageService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    // 统计每个群组的消息数
    public List<GroupMessageCount> getGroupMessageStats() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(
                Criteria.where("createTime").gt(
                    new Date(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000)
                )
            ),
            Aggregation.group("$to")
                .count().as("messageCount"),
            Aggregation.sort(Sort.by(Sort.Direction.DESC, "messageCount"))
        );
        
        AggregationResults<GroupMessageCount> results = mongoTemplate.aggregate(
            aggregation,
            "messages",
            GroupMessageCount.class
        );
        
        return results.getMappedResults();
    }
    
    // 自动清理旧消息
    public void deleteOldMessages(int daysToKeep) {
        Date deleteDate = new Date(System.currentTimeMillis() - daysToKeep * 24 * 60 * 60 * 1000);
        
        Query query = new Query();
        query.addCriteria(Criteria.where("createTime").lt(deleteDate));
        
        mongoTemplate.remove(query, "messages");
    }
    
    // 统计消息类型分布
    public Map<String, Long> getMessageTypeStats() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group("$type")
                .count().as("count")
        );
        
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation,
            "messages",
            Map.class
        );
        
        Map<String, Long> typeStats = new HashMap<>();
        for (Map result : results.getMappedResults()) {
            typeStats.put(
                (String) result.get("_id"),
                ((Number) result.get("count")).longValue()
            );
        }
        
        return typeStats;
    }
}

案例 2:用户活跃度排名

public List<UserActivity> getUserActivityRanking(Date startDate, Date endDate) {
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(
            new Criteria().andOperator(
                Criteria.where("createTime").gte(startDate),
                Criteria.where("createTime").lt(endDate)
            )
        ),
        Aggregation.group("$from")
            .count().as("messageCount")
            .addToSet("$to").as("groups"),
        Aggregation.project()
            .and("_id").as("userId")
            .include("messageCount")
            .andExpression("messageCount * 10").as("activityScore"),
        Aggregation.sort(Sort.by(Sort.Direction.DESC, "activityScore")),
        Aggregation.limit(100)
    );
    
    return mongoTemplate.aggregate(
        aggregation,
        "messages",
        UserActivity.class
    ).getMappedResults();
}

public class UserActivity {
    @Id
    private String userId;
    private int messageCount;
    private int activityScore;
    private List<String> groups;
}

案例 3:热门话题统计

public List<TopicTrend> getHotTopics() {
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.unwind("$tags"),
        Aggregation.group("$tags")
            .count().as("frequency")
            .avg("$likes").as("avgLikes"),
        Aggregation.sort(Sort.by(Sort.Direction.DESC, "frequency")),
        Aggregation.limit(20)
    );
    
    return mongoTemplate.aggregate(
        aggregation,
        "messages",
        TopicTrend.class
    ).getMappedResults();
}

第六部分:性能优化

1. 创建索引

@Document(collection = "messages")
public class Message {
    
    @Indexed  // 创建单字段索引
    private String from;
    
    @Indexed
    private String to;
    
    @Indexed
    private Date createTime;
    
    @CompoundIndex(name = "from_to_idx", def = "{'from': 1, 'to': 1}")
    private String id;
}

2. 查询优化建议

// ❌ 不好:一次查询大量数据
List<Message> all = mongoTemplate.findAll(Message.class);

// ✅ 好:使用分页
Query query = new Query()
    .with(new PageRequest(0, 1000));

// ❌ 不好:查询所有字段
Query query = new Query();

// ✅ 好:只查询需要的字段
Query query = new Query()
    .fields().include("from", "content");

// ❌ 不好:复杂的内存操作
List<Message> messages = mongoTemplate.findAll(Message.class);
List<String> senders = messages.stream()
    .map(Message::getFrom)
    .distinct()
    .collect(Collectors.toList());

// ✅ 好:使用聚合管道
Aggregation aggregation = Aggregation.newAggregation(
    Aggregation.group("$from")
);

3. 聚合优化建议

// ❌ 不好 - match 在 group 后
Aggregation.group("$from").count().as("count")
Aggregation.match(Criteria.where("count").gt(10))

// ✅ 好 - match 在最前
Aggregation.match(Criteria.where("type").is("text"))
Aggregation.group("$from").count().as("count")

4. 连接池配置

spring:
  data:
    mongodb:
      max-pool-size: 50
      min-pool-size: 10
      max-connection-idle-time: 60000
      max-connection-life-time: 300000
      wait-queue-timeout: 10000
posted @ 2026-08-07 17:11  Sappy  阅读(7)  评论(0)    收藏  举报