Java MongoDB 全栈教程:从入门到精通
本教程将引导你一步步学习如何使用 Java 驱动程序与 MongoDB 进行交互,我们将涵盖安装、连接、基本 CRUD(创建、读取、更新、删除)操作、聚合以及一些高级主题。

目录
- 准备工作
- 安装 MongoDB
- 安装 Java 和 Maven
- 创建第一个 Java 项目
使用 Maven 引入 MongoDB 驱动
- 连接到 MongoDB
MongoClient和MongoDatabase- 连接字符串
- 核心概念:集合与文档
- 什么是集合?
- 什么是文档?
- 使用
MongoCollection
- CRUD 操作详解
- Create: 插入文档 (
insertOne,insertMany) - Read: 查询文档 (
find,findOne) - Update: 更新文档 (
updateOne,updateMany) - Delete: 删除文档 (
deleteOne,deleteMany)
- Create: 插入文档 (
- 高级查询与聚合
- 使用
Filters进行复杂查询 - 使用
Projections选择返回字段 - 使用
Sort和Limit排序和限制结果 - 聚合管道简介 (
Aggregates)
- 使用
- 处理文档与 JSON
Document类- 使用 POJO (Plain Old Java Object)
- 索引
- 创建索引
- 查看索引
- 实战示例:一个简单的待办事项应用
- 总结与最佳实践
准备工作
在开始之前,请确保你的开发环境已经准备好。
a. 安装 MongoDB
如果你还没有安装 MongoDB,请按照官方指南进行安装:
安装完成后,启动 MongoDB 服务,默认情况下,它会在 localhost:27017 上运行。
b. 安装 Java 和 Maven
你需要安装 Java Development Kit (JDK) 8 或更高版本,以及 Apache Maven。
- JDK 下载: Oracle JDK 或 OpenJDK
- Maven 下载: Apache Maven 官网
确保 java 和 mvn 命令可以在你的终端/命令行中使用。
创建第一个 Java 项目
我们将使用 Maven 来管理项目依赖。
-
在你选择的目录下,打开终端。
-
运行以下 Maven 命令来创建一个快速原型项目:
mvn archetype:generate -DgroupId=com.example -DartifactId=mongodb-java-tutorial -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
-
进入项目目录:
cd mongodb-java-tutorial
-
打开
pom.xml文件,添加 MongoDB Java 驱动的依赖,我们使用最新的稳定版本(请务必检查官网获取最新版本)。<dependencies> <!-- MongoDB Java Driver --> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-sync</artifactId> <version>4.11.1</version> <!-- 请替换为最新版本 --> </dependency> <!-- JUnit for testing (可选) --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> -
你可以使用你的 IDE(如 IntelliJ IDEA 或 Eclipse)打开这个项目,或者直接在命令行中使用
mvn compile来编译项目。
连接到 MongoDB
连接是所有操作的第一步,MongoDB Java 驱动提供了非常简洁的 API。
创建一个新的 Java 类 MongoDBConnection.java。
package com.example;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
public class MongoDBConnection {
public static void main(String[] args) {
// 1. 定义连接字符串
// 默认连接到本地MongoDB,默认端口27017
String connectionString = "mongodb://localhost:27017";
// 2. 创建一个 MongoClient 实例
// MongoClient 是线程安全的,推荐在整个应用中只创建一个实例
try (MongoClient mongoClient = MongoClients.create(connectionString)) {
// 3. 获取数据库
// 如果数据库不存在,MongoDB 会在第一次插入数据时自动创建它
MongoDatabase database = mongoClient.getDatabase("myTestDB");
System.out.println("成功连接到数据库: " + database.getName());
// 你可以在这里进行后续操作...
// 获取一个集合
// database.getCollection("users");
} catch (Exception e) {
System.err.println("连接 MongoDB 时出错: " + e.getMessage());
e.printStackTrace();
}
}
}
代码解释:
MongoClients.create(connectionString): 这是创建连接的入口点,连接字符串mongodb://host:port指定了 MongoDB 服务器的地址。mongoClient.getDatabase("myTestDB"): 获取一个数据库对象,如果数据库不存在,MongoDB 不会立即创建它,而是在你第一次向该数据库中的集合写入数据时创建。try-with-resources:MongoClient实现了AutoCloseable,使用try-with-resources语句可以确保它在代码块执行完毕后自动关闭,防止资源泄漏。
核心概念:集合与文档
在 MongoDB 中,数据以 BSON (Binary JSON) 格式存储,类似于 JSON。
- 文档: 数据的基本单位,是一个键值对集合。
{ "name": "Alice", "age": 30 } - 集合: 文档的组,类似于关系型数据库中的表,集合中的文档结构可以不同(这是 MongoDB 的一个特点)。
在 Java 驱动中,我们使用 MongoCollection 来操作集合。
// 在连接代码之后
// 获取一个名为 "users" 的集合
// 如果集合不存在,在第一次插入文档时也会自动创建
com.mongodb.client.MongoCollection<org.bson.Document> collection = database.getCollection("users");
CRUD 操作详解
这是 MongoDB 操作的核心,我们将使用 MongoCollection<Document> 来进行演示。
C - Create (插入)
插入单个文档 (insertOne)
// 创建一个文档
Document doc = new Document("name", "John Doe")
.append("age", 30)
.append("email", "john.doe@example.com");
// 插入文档
collection.insertOne(doc);
System.out.println("文档已插入");
插入多个文档 (insertMany)
List<Document> documents = new ArrayList<>();
documents.add(new Document("name", "Alice").append("age", 25));
documents.add(new Document("name", "Bob").append("age", 35));
collection.insertMany(documents);
System.out.println("多个文档已插入");
R - Read (查询)
查找所有文档 (find)
find() 方法返回一个 FindIterable<Document>,你需要遍历它来获取结果。
// 查找集合中的所有文档
System.out.println("----- 查找所有用户 -----");
collection.find().forEach(doc -> System.out.println(doc.toJson()));
按条件查询 (find + Filters)
使用 Filters 类来构建查询条件,这是推荐的方式。
import static com.mongodb.client.model.Filters.*;
// 查找 name 为 "Alice" 的用户
System.out.println("----- 查找名为 Alice 的用户 -----");
collection.find(eq("name", "Alice")).forEach(doc -> System.out.println(doc.toJson()));
// 查找 age 大于 30 的用户
System.out.println("----- 查找年龄大于 30 的用户 -----");
collection.find(gt("age", 30)).forEach(doc -> System.out.println(doc.toJson()));
// 组合查询:查找 age 大于 30 且 name 为 "Bob" 的用户
System.out.println("----- 查找年龄大于 30 且名为 Bob 的用户 -----");
collection.find(and(gt("age", 30), eq("name", "Bob"))).forEach(doc -> System.out.println(doc.toJson()));
U - Update (更新)
更新单个文档 (updateOne)
更新 name 为 "Alice" 的用户,将其年龄设置为 26。
// 更新条件
Document query = new Document("name", "Alice");
// 更新操作
Document update = new Document("$set", new Document("age", 26));
// 执行更新
UpdateResult result = collection.updateOne(query, update);
System.out.println("匹配的文档数: " + result.getMatchedCount());
System.out.println("修改的文档数: " + result.getModifiedCount());
更新多个文档 (updateMany)
将所有年龄小于 30 的用户的 status 字段设置为 "young"。
// 更新条件
Document query = new Document("age", lt(30));
// 更新操作
Document update = new Document("$set", new Document("status", "young"));
// 执行更新
UpdateResult result = collection.updateMany(query, update);
System.out.println("匹配的文档数: " + result.getMatchedCount());
System.out.println("修改的文档数: " + result.getModifiedCount());
D - Delete (删除)
删除单个文档 (deleteOne)
删除 name 为 "John Doe" 的用户。
// 删除条件
Document query = new Document("name", "John Doe");
// 执行删除
DeleteResult result = collection.deleteOne(query);
System.out.println("删除的文档数: " + result.getDeletedCount());
删除多个文档 (deleteMany)
删除所有 status 为 "young" 的用户。
// 删除条件
Document query = new Document("status", "young");
// 执行删除
DeleteResult result = collection.deleteMany(query);
System.out.println("删除的文档数: " + result.getDeletedCount());
高级查询与聚合
使用 Projections 选择字段
默认情况下,find() 会返回文档的所有字段,你可以使用 Projections 来指定只返回需要的字段。
import static com.mongodb.client.model.Projections.*;
// 只返回 name 和 age 字段,不返回 _id
System.out.println("----- 只查询 name 和 age -----");
collection.find().projection(fields(include("name", "age"), excludeId()))
.forEach(doc -> System.out.println(doc.toJson()));
使用 Sort 和 Limit
import static com.mongodb.client.model.Sorts.*;
// 按 age 降序排序,并只返回前 2 个结果
System.out.println("----- 按年龄降序排序并限制结果 -----");
collection.find().sort(descending("age")).limit(2)
.forEach(doc -> System.out.println(doc.toJson()));
聚合管道 (Aggregates)
聚合是 MongoDB 中最强大的功能之一,它允许你对数据进行复杂的处理,如分组、计算、过滤等。
示例:按年龄分组并计算每个年龄的人数
import static com.mongodb.client.model.Aggregates.*;
List<Document> pipeline = Arrays.asList(
// 第一阶段:按 "age" 字段分组
group("$age", Accumulators.sum("count", 1)),
// 第二阶段:按 "count" 降序排序
sort(descending("count"))
);
// 执行聚合
collection.aggregate(pipeline).forEach(doc -> System.out.println(doc.toJson()));
处理文档与 JSON
到目前为止,我们一直在使用 org.bson.Document 类来表示文档,它非常灵活,但有时我们更希望使用强类型的 Java 对象(POJO)。
使用 POJO
-
创建一个 Java 类,其字段名与 BSON 文档的键名相匹配,你可以使用
@BsonProperty注解来处理名称不匹配的情况。package com.example; import org.bson.types.ObjectId; import org.bson.codecs.pojo.annotations.BsonId; import org.bson.codecs.pojo.annotations.BsonProperty; public class User { @BsonId // 标记此字段对应文档的 _id private ObjectId id; @BsonProperty("full_name") // 映射到 BSON 中的 "full_name" 字段 private String name; private int age; // 无参构造函数是必需的 public User() {} // getter 和 setter 方法 public ObjectId getId() { return id; } public void setId(ObjectId id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } } -
修改你的
MongoCollection以使用这个 POJO 类型。// 在连接数据库后 MongoCollection<User> userCollection = database.getCollection("users_pojos", User.class); // 插入 POJO User newUser = new User(); newUser.setName("Charlie Brown"); newUser.setAge(8); userCollection.insertOne(newUser); System.out.println("已插入 POJO: " + newUser); // 查询并获取 POJO userCollection.find(eq("name", "Charlie Brown")).forEach(user -> { System.out.println("查询到的 POJO: " + user); System.out.println("ID: " + user.getId()); // _id 会自动映射 });注意: 要使用 POJO,你的
MongoCollection的泛型类型必须是你的 POJO 类,并且需要确保你的项目中包含了 Jackson 或类似的库(驱动通常会自动处理)。User.class的存在告诉驱动如何将 BSON 反序列化为User对象。
索引
索引用于加速查询,如果没有索引,MongoDB 必须扫描集合中的所有文档(即全表扫描)。
创建索引
为 name 字段创建一个升序索引。
// 为 "name" 字段创建索引
collection.createIndex(new Document("name", 1)); // 1 表示升序, -1 表示降序
System.out.println("索引已创建");
查看集合的索引
// 获取集合的所有索引信息 collection.listIndexes().forEach(index -> System.out.println(index.toJson()));
实战示例:一个简单的待办事项应用
让我们整合所学知识,创建一个简单的 To-Do List 应用。
TodoApp.java
package com.example;
import com.mongodb.client.*;
import com.mongodb.client.model.*;
import org.bson.Document;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Sorts.*;
public class TodoApp {
private static final String DB_NAME = "todoDB";
private static final String COLLECTION_NAME = "tasks";
private static MongoCollection<Document> collection;
public static void main(String[] args) {
// 1. 连接
try (MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017")) {
MongoDatabase database = mongoClient.getDatabase(DB_NAME);
collection = database.getCollection(COLLECTION_NAME);
System.out.println("成功连接到 " + DB_NAME);
// 2. 初始化一些数据 (可选)
initializeData();
// 3. 执行 CRUD 操作
System.out.println("\n--- 所有任务 ---");
findAllTasks();
System.out.println("\n--- 添加新任务 ---");
addTask("学习 Java MongoDB", false);
addTask("去健身房", true);
System.out.println("\n--- 更新任务状态 ---");
markTaskAsCompleted("学习 Java MongoDB");
System.out.println("\n--- 查找所有未完成的任务 ---");
findIncompleteTasks();
System.out.println("\n--- 删除已完成的任务 ---");
deleteCompletedTasks();
System.out.println("\n--- 最终所有任务 ---");
findAllTasks();
}
}
private static void initializeData() {
// 清空旧数据,以便每次运行都干净
collection.deleteMany(new Document());
List<Document> initialTasks = Arrays.asList(
new Document("title", "完成项目报告").append("completed", false),
new Document("title", "购买杂货").append("completed", true)
);
collection.insertMany(initialTasks);
System.out.println("初始化数据完成。");
}
private static void findAllTasks() {
collection.find().sort(ascending("title")).forEach(doc -> System.out.println(doc.toJson()));
}
private static void addTask(String title, boolean completed) {
Document task = new Document("title", title).append("completed", completed);
collection.insertOne(task);
System.out.println("已添加任务: " + title);
}
private static void markTaskAsCompleted(String title) {
UpdateResult result = collection.updateOne(eq("title", title), new Document("$set", new Document("completed", true)));
if (result.getMatchedCount() > 0) {
System.out.println("任务 '" + title + "' 已标记为完成。");
} else {
System.out.println("未找到任务: " + title);
}
}
private static void findIncompleteTasks() {
collection.find(eq("completed", false)).forEach(doc -> System.out.println(doc.toJson()));
}
private static void deleteCompletedTasks() {
DeleteResult result = collection.deleteMany(eq("completed", true));
System.out.println("已删除 " + result.getDeletedCount() + " 个已完成的任务。");
}
}
总结与最佳实践
- 连接管理:
MongoClient是重量级且线程安全的,在你的应用中只创建一个实例,并在应用关闭时关闭它。 - 异常处理: 总是使用
try-catch块来处理MongoException及其子类,例如网络问题、查询错误等。 - 使用
Filters和Projections: 使用驱动提供的Filters,Projections,Sorts,Aggregates等工具类,而不是手动构建Document查询,这更安全、更易读。 - POJO vs.
Document: 对于结构固定的数据,优先使用 POJO,它提供了类型安全和更好的 IDE 支持,对于动态或模式不固定的数据,Document更灵活。 - 索引: 对于频繁查询的字段,务必创建索引,但要注意,索引会占用存储空间并降低写入速度,因此要权衡使用。
- 版本兼容性: 确保你的 MongoDB Java 驱动版本与你的 MongoDB 服务器版本兼容,可以参考 MongoDB 驱动兼容性矩阵。
希望这份详细的教程能帮助你顺利掌握 Java 与 MongoDB 的开发!
