MongoDB + Java 使用
MongoDB + Java 使用
目录
第一部分:基础配置
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

浙公网安备 33010602011771号