MongoDB 詳細用法與 Java 集成完整指南
目錄
- MongoDB 基礎概念
- MongoDB 安裝與配置
- MongoDB Shell 基本操作
- Java 環境準備
- Java MongoDB 驅動集成
- 連接配置
- 基本 CRUD 操作
- 高級查詢操作
- 索引操作
- 聚合管道
- 事務處理
- Spring Boot 集成
- 最佳實踐
1. MongoDB 基礎概念
1.1 核心概念對比
SQL術語 | MongoDB術語 | 說明 |
---|---|---|
database | database | 數據庫 |
table | collection | 集合(表) |
row | document | 文檔(行) |
column | field | 字段(列) |
index | index | 索引 |
primary key | _id | 主鍵 |
1.2 文檔結構
{"_id": ObjectId("507f1f77bcf86cd799439011"),"name": "John Doe","age": 30,"email": "john@example.com","address": {"street": "123 Main St","city": "New York","zipcode": "10001"},"tags": ["developer", "mongodb", "java"],"createdAt": ISODate("2023-01-01T00:00:00Z")
}
2. MongoDB 安裝與配置
2.1 Windows 安裝
# 1. 下載 MongoDB Community Server
# https://www.mongodb.com/try/download/community# 2. 安裝后啟動服務
net start MongoDB# 3. 連接到 MongoDB
mongo
2.2 Linux 安裝 (Ubuntu)
# 1. 導入公鑰
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -# 2. 添加倉庫
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list# 3. 更新包列表并安裝
sudo apt-get update
sudo apt-get install -y mongodb-org# 4. 啟動服務
sudo systemctl start mongod
sudo systemctl enable mongod# 5. 驗證安裝
mongosh
2.3 Docker 安裝
# 拉取 MongoDB 鏡像
docker pull mongo:latest# 運行 MongoDB 容器
docker run -d \--name mongodb \-p 27017:27017 \-e MONGO_INITDB_ROOT_USERNAME=admin \-e MONGO_INITDB_ROOT_PASSWORD=password \-v mongodb_data:/data/db \mongo:latest# 連接到容器
docker exec -it mongodb mongosh -u admin -p password
3. MongoDB Shell 基本操作
3.1 數據庫操作
// 顯示所有數據庫
show dbs// 使用/創建數據庫
use myapp// 顯示當前數據庫
db// 刪除數據庫
db.dropDatabase()
3.2 集合操作
// 顯示所有集合
show collections// 創建集合
db.createCollection("users")// 刪除集合
db.users.drop()
3.3 文檔操作
// 插入單個文檔
db.users.insertOne({name: "Alice",age: 25,email: "alice@example.com"
})// 插入多個文檔
db.users.insertMany([{ name: "Bob", age: 30, email: "bob@example.com" },{ name: "Charlie", age: 35, email: "charlie@example.com" }
])// 查詢所有文檔
db.users.find()// 條件查詢
db.users.find({ age: { $gte: 30 } })// 更新文檔
db.users.updateOne({ name: "Alice" },{ $set: { age: 26 } }
)// 刪除文檔
db.users.deleteOne({ name: "Bob" })
4. Java 環境準備
4.1 系統要求
- Java 8 或更高版本
- Maven 3.3+ 或 Gradle 4.0+
- MongoDB 4.0 或更高版本
4.2 Maven 項目結構
my-mongodb-app/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── Main.java
│ │ │ ├── config/
│ │ │ ├── model/
│ │ │ ├── dao/
│ │ │ └── service/
│ │ └── resources/
│ │ └── application.properties
│ └── test/
└── target/
5. Java MongoDB 驅動集成
5.1 Maven 依賴配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>mongodb-java-example</artifactId><version>1.0.0</version><packaging>jar</packaging><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- MongoDB Java Driver --><dependency><groupId>org.mongodb</groupId><artifactId>mongodb-driver-sync</artifactId><version>4.11.1</version></dependency><!-- 可選: 異步驅動 --><dependency><groupId>org.mongodb</groupId><artifactId>mongodb-driver-reactivestreams</artifactId><version>4.11.1</version></dependency><!-- JSON 處理 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.2</version></dependency><!-- 日志 --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>2.0.7</version></dependency><!-- 測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version><configuration><source>11</source><target>11</target></configuration></plugin></plugins></build>
</project>
5.2 Gradle 依賴配置
plugins {id 'java'id 'application'
}group = 'com.example'
version = '1.0.0'
sourceCompatibility = '11'repositories {mavenCentral()
}dependencies {// MongoDB Java Driverimplementation 'org.mongodb:mongodb-driver-sync:4.11.1'implementation 'org.mongodb:mongodb-driver-reactivestreams:4.11.1'// JSON 處理implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'// 日志implementation 'org.slf4j:slf4j-simple:2.0.7'// 測試testImplementation 'junit:junit:4.13.2'
}application {mainClass = 'com.example.Main'
}
6. 連接配置
6.1 基本連接配置
package com.example.config;import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.ServerAddress;
import com.mongodb.MongoCredential;
import java.util.Arrays;public class MongoDBConfig {private static final String DATABASE_NAME = "myapp";private static final String CONNECTION_STRING = "mongodb://localhost:27017";private static MongoClient mongoClient;private static MongoDatabase database;// 簡單連接方式public static MongoDatabase getDatabase() {if (database == null) {mongoClient = MongoClients.create(CONNECTION_STRING);database = mongoClient.getDatabase(DATABASE_NAME);}return database;}// 詳細連接配置public static MongoDatabase getDatabaseWithOptions() {if (database == null) {// 創建連接字符串ConnectionString connectionString = new ConnectionString("mongodb://username:password@localhost:27017/myapp?authSource=admin");// 或者使用 MongoClientSettings 進行詳細配置MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(connectionString).retryWrites(true).build();mongoClient = MongoClients.create(settings);database = mongoClient.getDatabase(DATABASE_NAME);}return database;}// 集群連接配置public static MongoDatabase getClusterDatabase() {if (database == null) {// 服務器地址列表ServerAddress seed1 = new ServerAddress("server1.example.com", 27017);ServerAddress seed2 = new ServerAddress("server2.example.com", 27017);ServerAddress seed3 = new ServerAddress("server3.example.com", 27017);// 認證信息MongoCredential credential = MongoCredential.createCredential("username", "admin", "password".toCharArray());// 客戶端設置MongoClientSettings settings = MongoClientSettings.builder().applyToClusterSettings(builder -> builder.hosts(Arrays.asList(seed1, seed2, seed3))).credential(credential).build();mongoClient = MongoClients.create(settings);database = mongoClient.getDatabase(DATABASE_NAME);}return database;}// 關閉連接public static void close() {if (mongoClient != null) {mongoClient.close();}}
}
6.2 連接池配置
package com.example.config;import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.connection.ConnectionPoolSettings;
import java.util.concurrent.TimeUnit;public class ConnectionPoolConfig {public static MongoClient createMongoClientWithPool() {ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/myapp");// 連接池設置ConnectionPoolSettings poolSettings = ConnectionPoolSettings.builder().maxSize(20) // 最大連接數.minSize(5) // 最小連接數.maxWaitTime(2, TimeUnit.MINUTES) // 最大等待時間.maxConnectionLifeTime(30, TimeUnit.MINUTES) // 連接最大生存時間.maxConnectionIdleTime(10, TimeUnit.MINUTES) // 連接最大空閑時間.build();MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(connectionString).applyToConnectionPoolSettings(builder -> builder.applySettings(poolSettings)).build();return MongoClients.create(settings);}
}
7. 基本 CRUD 操作
7.1 實體類定義
package com.example.model;import org.bson.types.ObjectId;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
import java.util.List;public class User {@JsonProperty("_id")private ObjectId id;private String name;private String email;private Integer age;private Address address;private List<String> tags;private LocalDateTime createdAt;private LocalDateTime updatedAt;// 構造函數public User() {this.createdAt = LocalDateTime.now();}public User(String name, String email, Integer age) {this();this.name = name;this.email = email;this.age = age;}// 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 String getEmail() { return email; }public void setEmail(String email) { this.email = email; }public Integer getAge() { return age; }public void setAge(Integer age) { this.age = age; }public Address getAddress() { return address; }public void setAddress(Address address) { this.address = address; }public List<String> getTags() { return tags; }public void setTags(List<String> tags) { this.tags = tags; }public LocalDateTime getCreatedAt() { return createdAt; }public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }public LocalDateTime getUpdatedAt() { return updatedAt; }public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", email='" + email + '\'' +", age=" + age +", address=" + address +", tags=" + tags +", createdAt=" + createdAt +", updatedAt=" + updatedAt +'}';}
}// 地址類
class Address {private String street;private String city;private String zipcode;private String country;// 構造函數public Address() {}public Address(String street, String city, String zipcode, String country) {this.street = street;this.city = city;this.zipcode = zipcode;this.country = country;}// Getter 和 Setter 方法public String getStreet() { return street; }public void setStreet(String street) { this.street = street; }public String getCity() { return city; }public void setCity(String city) { this.city = city; }public String getZipcode() { return zipcode; }public void setZipcode(String zipcode) { this.zipcode = zipcode; }public String getCountry() { return country; }public void setCountry(String country) { this.country = country; }@Overridepublic String toString() {return "Address{" +"street='" + street + '\'' +", city='" + city + '\'' +", zipcode='" + zipcode + '\'' +", country='" + country + '\'' +'}';}
}
7.2 DAO 層實現
package com.example.dao;import com.example.config.MongoDBConfig;
import com.example.model.User;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import com.mongodb.client.model.Sorts;
import com.mongodb.client.model.Projections;
import com.mongodb.client.result.InsertOneResult;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.client.result.DeleteResult;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;public class UserDAO {private final MongoCollection<Document> collection;public UserDAO() {MongoDatabase database = MongoDBConfig.getDatabase();this.collection = database.getCollection("users");}// 創建用戶 - 插入單個文檔public ObjectId createUser(User user) {try {Document doc = userToDocument(user);InsertOneResult result = collection.insertOne(doc);return result.getInsertedId().asObjectId().getValue();} catch (Exception e) {throw new RuntimeException("Error creating user: " + e.getMessage(), e);}}// 批量創建用戶public List<ObjectId> createUsers(List<User> users) {try {List<Document> documents = new ArrayList<>();for (User user : users) {documents.add(userToDocument(user));}collection.insertMany(documents);List<ObjectId> insertedIds = new ArrayList<>();for (Document doc : documents) {insertedIds.add(doc.getObjectId("_id"));}return insertedIds;} catch (Exception e) {throw new RuntimeException("Error creating users: " + e.getMessage(), e);}}// 根據 ID 查詢用戶public User findById(ObjectId id) {try {Document doc = collection.find(Filters.eq("_id", id)).first();return doc != null ? documentToUser(doc) : null;} catch (Exception e) {throw new RuntimeException("Error finding user by id: " + e.getMessage(), e);}}// 根據郵箱查詢用戶public User findByEmail(String email) {try {Document doc = collection.find(Filters.eq("email", email)).first();return doc != null ? documentToUser(doc) : null;} catch (Exception e) {throw new RuntimeException("Error finding user by email: " + e.getMessage(), e);}}// 查詢所有用戶public List<User> findAll() {try {List<User> users = new ArrayList<>();for (Document doc : collection.find()) {users.add(documentToUser(doc));}return users;} catch (Exception e) {throw new RuntimeException("Error finding all users: " + e.getMessage(), e);}}// 條件查詢 - 年齡范圍public List<User> findByAgeRange(int minAge, int maxAge) {try {List<User> users = new ArrayList<>();Bson filter = Filters.and(Filters.gte("age", minAge),Filters.lte("age", maxAge));for (Document doc : collection.find(filter)) {users.add(documentToUser(doc));}return users;} catch (Exception e) {throw new RuntimeException("Error finding users by age range: " + e.getMessage(), e);}}// 模糊查詢 - 姓名包含關鍵詞public List<User> findByNameContaining(String keyword) {try {List<User> users = new ArrayList<>();Bson filter = Filters.regex("name", ".*" + keyword + ".*", "i");for (Document doc : collection.find(filter)) {users.add(documentToUser(doc));}return users;} catch (Exception e) {throw new RuntimeException("Error finding users by name: " + e.getMessage(), e);}}// 分頁查詢public List<User> findWithPagination(int page, int pageSize) {try {List<User> users = new ArrayList<>();int skip = (page - 1) * pageSize;for (Document doc : collection.find().sort(Sorts.descending("createdAt")).skip(skip).limit(pageSize)) {users.add(documentToUser(doc));}return users;} catch (Exception e) {throw new RuntimeException("Error finding users with pagination: " + e.getMessage(), e);}}// 只查詢特定字段public List<User> findNamesAndEmails() {try {List<User> users = new ArrayList<>();Bson projection = Projections.fields(Projections.include("name", "email"),Projections.exclude("_id"));for (Document doc : collection.find().projection(projection)) {User user = new User();user.setName(doc.getString("name"));user.setEmail(doc.getString("email"));users.add(user);}return users;} catch (Exception e) {throw new RuntimeException("Error finding names and emails: " + e.getMessage(), e);}}// 更新用戶public boolean updateUser(ObjectId id, User user) {try {user.setUpdatedAt(LocalDateTime.now());Bson filter = Filters.eq("_id", id);Bson update = Updates.combine(Updates.set("name", user.getName()),Updates.set("email", user.getEmail()),Updates.set("age", user.getAge()),Updates.set("updatedAt", user.getUpdatedAt()));UpdateResult result = collection.updateOne(filter, update);return result.getModifiedCount() > 0;} catch (Exception e) {throw new RuntimeException("Error updating user: " + e.getMessage(), e);}}// 部分更新 - 只更新年齡public boolean updateAge(ObjectId id, int newAge) {try {Bson filter = Filters.eq("_id", id);Bson update = Updates.combine(Updates.set("age", newAge),Updates.set("updatedAt", LocalDateTime.now()));UpdateResult result = collection.updateOne(filter, update);return result.getModifiedCount() > 0;} catch (Exception e) {throw new RuntimeException("Error updating user age: " + e.getMessage(), e);}}// 批量更新public long updateUsersInCity(String city, String newCountry) {try {Bson filter = Filters.eq("address.city", city);Bson update = Updates.combine(Updates.set("address.country", newCountry),Updates.set("updatedAt", LocalDateTime.now()));UpdateResult result = collection.updateMany(filter, update);return result.getModifiedCount();} catch (Exception e) {throw new RuntimeException("Error updating users in city: " + e.getMessage(), e);}}// 刪除用戶public boolean deleteUser(ObjectId id) {try {DeleteResult result = collection.deleteOne(Filters.eq("_id", id));return result.getDeletedCount() > 0;} catch (Exception e) {throw new RuntimeException("Error deleting user: " + e.getMessage(), e);}}// 根據條件刪除多個用戶public long deleteUsersByAge(int maxAge) {try {Bson filter = Filters.lte("age", maxAge);DeleteResult result = collection.deleteMany(filter);return result.getDeletedCount();} catch (Exception e) {throw new RuntimeException("Error deleting users by age: " + e.getMessage(), e);}}// 計數操作public long countUsers() {try {return collection.countDocuments();} catch (Exception e) {throw new RuntimeException("Error counting users: " + e.getMessage(), e);}}public long countUsersByAge(int minAge) {try {return collection.countDocuments(Filters.gte("age", minAge));} catch (Exception e) {throw new RuntimeException("Error counting users by age: " + e.getMessage(), e);}}// 輔助方法 - User 轉 Documentprivate Document userToDocument(User user) {Document doc = new Document();if (user.getId() != null) {doc.put("_id", user.getId());}doc.put("name", user.getName());doc.put("email", user.getEmail());doc.put("age", user.getAge());if (user.getAddress() != null) {Document addressDoc = new Document().append("street", user.getAddress().getStreet()).append("city", user.getAddress().getCity()).append("zipcode", user.getAddress().getZipcode()).append("country", user.getAddress().getCountry());doc.put("address", addressDoc);}if (user.getTags() != null) {doc.put("tags", user.getTags());}doc.put("createdAt", user.getCreatedAt());doc.put("updatedAt", user.getUpdatedAt());return doc;}// 輔助方法 - Document 轉 Userprivate User documentToUser(Document doc) {User user = new User();user.setId(doc.getObjectId("_id"));user.setName(doc.getString("name"));user.setEmail(doc.getString("email"));user.setAge(doc.getInteger("age"));Document addressDoc = doc.get("address", Document.class);if (addressDoc != null) {Address address = new Address(addressDoc.getString("street"),addressDoc.getString("city"),addressDoc.getString("zipcode"),addressDoc.getString("country"));user.setAddress(address);}@SuppressWarnings("unchecked")List<String> tags = doc.get("tags", List.class);user.setTags(tags);// 注意:這里需要處理日期類型轉換user.setCreatedAt((LocalDateTime) doc.get("createdAt"));user.setUpdatedAt((LocalDateTime) doc.get("updatedAt"));return user;}
}
7.3 Service 層實現
package com.example.service;import com.example.dao.UserDAO;
import com.example.model.User;
import org.bson.types.ObjectId;import java.util.List;public class UserService {private final UserDAO userDAO;public UserService() {this.userDAO = new UserDAO();}// 創建用戶public ObjectId createUser(String name, String email, Integer age) {// 業務邏輯驗證if (name == null || name.trim().isEmpty()) {throw new IllegalArgumentException("Name cannot be empty");}if (email == null || !isValidEmail(email)) {throw new IllegalArgumentException("Invalid email format");}if (age != null && (age < 0 || age > 150)) {throw new IllegalArgumentException("Age must be between 0 and 150");}// 檢查郵箱是否已存在User existingUser = userDAO.findByEmail(email);if (existingUser != null) {throw new IllegalArgumentException("Email already exists");}User user = new User(name, email, age);return userDAO.createUser(user);}// 獲取用戶public User getUserById(String id) {try {ObjectId objectId = new ObjectId(id);return userDAO.findById(objectId);} catch (IllegalArgumentException e) {throw new IllegalArgumentException("Invalid user ID format");}}public User getUserByEmail(String email) {return userDAO.findByEmail(email);}public List<User> getAllUsers() {return userDAO.findAll();}public List<User> getUsersByAgeRange(int minAge, int maxAge) {if (minAge < 0 || maxAge < 0 || minAge > maxAge) {throw new IllegalArgumentException("Invalid age range");}return userDAO.findByAgeRange(minAge, maxAge);}public List<User> searchUsersByName(String keyword) {if (keyword == null || keyword.trim().isEmpty()) {throw new IllegalArgumentException("Search keyword cannot be empty");}return userDAO.findByNameContaining(keyword.trim());}public List<User> getUsersWithPagination(int page, int pageSize) {if (page < 1 || pageSize < 1) {throw new IllegalArgumentException("Page and pageSize must be positive");}return userDAO.findWithPagination(page, pageSize);}// 更新用戶public boolean updateUser(String id, String name, String email, Integer age) {try {ObjectId objectId = new ObjectId(id);// 驗證輸入if (name != null && name.trim().isEmpty()) {throw new IllegalArgumentException("Name cannot be empty");}if (email != null && !isValidEmail(email)) {throw new IllegalArgumentException("Invalid email format");}if (age != null && (age < 0 || age > 150)) {throw new IllegalArgumentException("Age must be between 0 and 150");}// 檢查用戶是否存在User existingUser = userDAO.findById(objectId);if (existingUser == null) {throw new IllegalArgumentException("User not found");}// 如果更新郵箱,檢查新郵箱是否已被其他用戶使用if (email != null && !email.equals(existingUser.getEmail())) {User userWithEmail = userDAO.findByEmail(email);if (userWithEmail != null && !userWithEmail.getId().equals(objectId)) {throw new IllegalArgumentException("Email already exists");}}// 更新用戶信息if (name != null) existingUser.setName(name);if (email != null) existingUser.setEmail(email);if (age != null) existingUser.setAge(age);return userDAO.updateUser(objectId, existingUser);} catch (IllegalArgumentException e) {throw new IllegalArgumentException("Invalid user ID format");}}public boolean updateUserAge(String id, int newAge) {try {ObjectId objectId = new ObjectId(id);if (newAge < 0 || newAge > 150) {throw new IllegalArgumentException("Age must be between 0 and 150");}return userDAO.updateAge(objectId, newAge);} catch (IllegalArgumentException e) {throw new IllegalArgumentException("Invalid user ID format");}}// 刪除用戶public boolean deleteUser(String id) {try {ObjectId objectId = new ObjectId(id);// 檢查用戶是否存在User existingUser = userDAO.findById(objectId);if (existingUser == null) {throw new IllegalArgumentException("User not found");}return userDAO.deleteUser(objectId);} catch (IllegalArgumentException e) {throw new IllegalArgumentException("Invalid user ID format");}}// 統計操作public long getUserCount() {return userDAO.countUsers();}public long getAdultUserCount() {return userDAO.countUsersByAge(18);}// 批量操作public List<ObjectId> createUsers(List<User> users) {// 驗證所有用戶數據for (User user : users) {if (user.getName() == null || user.getName().trim().isEmpty()) {throw new IllegalArgumentException("All users must have a name");}if (user.getEmail() == null || !isValidEmail(user.getEmail())) {throw new IllegalArgumentException("All users must have a valid email");}}return userDAO.createUsers(users);}public long bulkDeleteUsersByAge(int maxAge) {if (maxAge < 0) {throw new IllegalArgumentException("Age must be non-negative");}return userDAO.deleteUsersByAge(maxAge);}// 郵箱格式驗證private boolean isValidEmail(String email) {return email != null && email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");}
}
8. 高級查詢操作
8.1 復雜查詢實現
package com.example.dao;import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.*;
import org.bson.Document;
import org.bson.conversions.Bson;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;public class AdvancedQueryDAO {private final MongoCollection<Document> collection;public AdvancedQueryDAO(MongoCollection<Document> collection) {this.collection = collection;}// 復合條件查詢public List<Document> findUsersWithComplexConditions() {List<Document> results = new ArrayList<>();// 年齡在20-40之間,且名字包含"John"或郵箱以"gmail"結尾Bson ageFilter = Filters.and(Filters.gte("age", 20),Filters.lte("age", 40));Bson nameOrEmailFilter = Filters.or(Filters.regex("name", ".*John.*", "i"),Filters.regex("email", ".*@gmail\\.com$", "i"));Bson complexFilter = Filters.and(ageFilter, nameOrEmailFilter);for (Document doc : collection.find(complexFilter)) {results.add(doc);}return results;}// 數組查詢public List<Document> findUsersByTags(String... tags) {List<Document> results = new ArrayList<>();// 包含所有指定標簽的用戶Bson filter = Filters.all("tags", Arrays.asList(tags));for (Document doc : collection.find(filter)) {results.add(doc);}return results;}public List<Document> findUsersByAnyTag(String... tags) {List<Document> results = new ArrayList<>();// 包含任意一個指定標簽的用戶Bson filter = Filters.in("tags", Arrays.asList(tags));for (Document doc : collection.find(filter)) {results.add(doc);}return results;}// 嵌套文檔查詢public List<Document> findUsersByCity(String city) {List<Document> results = new ArrayList<>();Bson filter = Filters.eq("address.city", city);for (Document doc : collection.find(filter)) {results.add(doc);}return results;}// 存在性查詢public List<Document> findUsersWithAddress() {List<Document> results = new ArrayList<>();Bson filter = Filters.exists("address");for (Document doc : collection.find(filter)) {results.add(doc);}return results;}public List<Document> findUsersWithoutTags() {List<Document> results = new ArrayList<>();Bson filter = Filters.or(Filters.exists("tags", false),Filters.size("tags", 0));for (Document doc : collection.find(filter)) {results.add(doc);}return results;}// 正則表達式查詢public List<Document> findUsersByEmailDomain(String domain) {List<Document> results = new ArrayList<>();Pattern pattern = Pattern.compile(".*@" + Pattern.quote(domain) + "$", Pattern.CASE_INSENSITIVE);Bson filter = Filters.regex("email", pattern);for (Document doc : collection.find(filter)) {results.add(doc);}return results;}// 類型查詢public List<Document> findUsersWithStringAge() {List<Document> results = new ArrayList<>();Bson filter = Filters.type("age", "string");for (Document doc : collection.find(filter)) {results.add(doc);}return results;}// 地理位置查詢(需要創建地理索引)public List<Document> findUsersNearLocation(double longitude, double latitude, double maxDistance) {List<Document> results = new ArrayList<>();Bson filter = Filters.near("location", longitude, latitude, maxDistance, null);for (Document doc : collection.find(filter)) {results.add(doc);}return results;}// 排序和限制public List<Document> findTopUsersByAge(int limit) {List<Document> results = new ArrayList<>();for (Document doc : collection.find().sort(Sorts.descending("age")).limit(limit)) {results.add(doc);}return results;}// 多字段排序public List<Document> findUsersOrderedByAgeAndName() {List<Document> results = new ArrayList<>();Bson sort = Sorts.orderBy(Sorts.ascending("age"),Sorts.ascending("name"));for (Document doc : collection.find().sort(sort)) {results.add(doc);}return results;}
}
9. 索引操作
9.1 索引管理類
package com.example.index;import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import org.bson.Document;import java.util.concurrent.TimeUnit;public class IndexManager {private final MongoCollection<Document> collection;public IndexManager(MongoCollection<Document> collection) {this.collection = collection;}// 創建單字段索引public void createSingleFieldIndex() {// 在 email 字段上創建升序索引collection.createIndex(Indexes.ascending("email"));System.out.println("Created ascending index on email field");// 在 age 字段上創建降序索引collection.createIndex(Indexes.descending("age"));System.out.println("Created descending index on age field");}// 創建唯一索引public void createUniqueIndex() {IndexOptions indexOptions = new IndexOptions().unique(true);collection.createIndex(Indexes.ascending("email"), indexOptions);System.out.println("Created unique index on email field");}// 創建復合索引public void createCompoundIndex() {collection.createIndex(Indexes.compoundIndex(Indexes.ascending("age"),Indexes.descending("createdAt")));System.out.println("Created compound index on age (asc) and createdAt (desc)");}// 創建文本索引public void createTextIndex() {collection.createIndex(Indexes.compoundIndex(Indexes.text("name"),Indexes.text("email")));System.out.println("Created text index on name and email fields");}// 創建部分索引public void createPartialIndex() {IndexOptions indexOptions = new IndexOptions().partialFilterExpression(new Document("age", new Document("$gte", 18)));collection.createIndex(Indexes.ascending("email"), indexOptions);System.out.println("Created partial index on email for users >= 18 years old");}// 創建 TTL 索引(生存時間索引)public void createTTLIndex() {IndexOptions indexOptions = new IndexOptions().expireAfter(30L, TimeUnit.DAYS);collection.createIndex(Indexes.ascending("createdAt"), indexOptions);System.out.println("Created TTL index on createdAt field (30 days)");}// 創建稀疏索引public void createSparseIndex() {IndexOptions indexOptions = new IndexOptions().sparse(true);collection.createIndex(Indexes.ascending("phoneNumber"), indexOptions);System.out.println("Created sparse index on phoneNumber field");}// 創建地理空間索引public void createGeospatialIndex() {collection.createIndex(Indexes.geo2dsphere("location"));System.out.println("Created 2dsphere index on location field");}// 創建哈希索引public void createHashedIndex() {collection.createIndex(Indexes.hashed("userId"));System.out.println("Created hashed index on userId field");}// 列出所有索引public void listAllIndexes() {System.out.println("Existing indexes:");for (Document index : collection.listIndexes()) {System.out.println(index.toJson());}}// 刪除索引public void dropIndexes() {// 刪除特定索引collection.dropIndex("email_1");System.out.println("Dropped index on email field");// 刪除所有索引(除了 _id 索引)// collection.dropIndexes();}// 獲取索引統計信息public void getIndexStats() {// 這需要通過聚合管道來獲取System.out.println("Index statistics would be retrieved through aggregation pipeline");}
}
10. 聚合管道
10.1 聚合操作實現
package com.example.aggregation;import com.mongodb.client.MongoCollection;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Sorts;
import org.bson.Document;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class AggregationOperations {private final MongoCollection<Document> collection;public AggregationOperations(MongoCollection<Document> collection) {this.collection = collection;}// 基本統計聚合public Document getUserStatistics() {List<Document> pipeline = Arrays.asList(new Document("$group", new Document("_id", null).append("totalUsers", new Document("$sum", 1)).append("averageAge", new Document("$avg", "$age")).append("minAge", new Document("$min", "$age")).append("maxAge", new Document("$max", "$age")).append("totalAge", new Document("$sum", "$age"))));AggregateIterable<Document> result = collection.aggregate(pipeline);return result.first();}// 按年齡分組統計public List<Document> getUsersByAgeGroup() {List<Document> results = new ArrayList<>();List<Document> pipeline = Arrays.asList(new Document("$group", new Document("_id", new Document("$switch", new Document("branches", Arrays.asList(new Document("case", new Document("$lt", Arrays.asList("$age", 18))).append("then", "未成年"),new Document("case", new Document("$lt", Arrays.asList("$age", 35))).append("then", "青年"),new Document("case", new Document("$lt", Arrays.asList("$age", 60))).append("then", "中年"))).append("default", "老年"))).append("count", new Document("$sum", 1)).append("averageAge", new Document("$avg", "$age"))),new Document("$sort", new Document("count", -1)));for (Document doc : collection.aggregate(pipeline)) {results.add(doc);}return results;}// 按城市分組統計public List<Document> getUsersByCity() {List<Document> results = new ArrayList<>();List<Document> pipeline = Arrays.asList(new Document("$match", new Document("address.city", new Document("$exists", true))),new Document("$group", new Document("_id", "$address.city").append("userCount", new Document("$sum", 1)).append("averageAge", new Document("$avg", "$age")).append("users", new Document("$push", new Document("name", "$name").append("email", "$email")))),new Document("$sort", new Document("userCount", -1)));for (Document doc : collection.aggregate(pipeline)) {results.add(doc);}return results;}// 投影操作 - 重構輸出格式public List<Document> getUserProfiles() {List<Document> results = new ArrayList<>();List<Document> pipeline = Arrays.asList(new Document("$project", new Document("_id", 0).append("fullName", "$name").append("contactInfo", new Document("email", "$email").append("phone", "$phoneNumber")).append("demographics", new Document("age", "$age").append("ageGroup", new Document("$switch", new Document("branches", Arrays.asList(new Document("case", new Document("$lt", Arrays.asList("$age", 25))).append("then", "Young"),new Document("case", new Document("$lt", Arrays.asList("$age", 50))).append("then", "Middle-aged"))).append("default", "Senior")))).append("location", "$address.city").append("isActive", new Document("$cond", Arrays.asList(new Document("$ne", Arrays.asList("$lastLoginDate", null)),true,false)))));for (Document doc : collection.aggregate(pipeline)) {results.add(doc);}return results;}// 查找操作 - 關聯其他集合public List<Document> getUsersWithOrderInfo(MongoCollection<Document> ordersCollection) {List<Document> results = new ArrayList<>();List<Document> pipeline = Arrays.asList(new Document("$lookup", new Document("from", "orders").append("localField", "_id").append("foreignField", "userId").append("as", "orders")),new Document("$addFields", new Document("orderCount", new Document("$size", "$orders")).append("totalOrderValue", new Document("$sum", "$orders.amount"))),new Document("$match", new Document("orderCount", new Document("$gt", 0))),new Document("$sort", new Document("totalOrderValue", -1)));for (Document doc : collection.aggregate(pipeline)) {results.add(doc);}return results;}// 解構數組操作public List<Document> getUserTagAnalysis() {List<Document> results = new ArrayList<>();List<Document> pipeline = Arrays.asList(new Document("$match", new Document("tags", new Document("$exists", true))),new Document("$unwind", "$tags"),new Document("$group", new Document("_id", "$tags").append("userCount", new Document("$sum", 1)).append("users", new Document("$push", "$name"))),new Document("$sort", new Document("userCount", -1)),new Document("$limit", 10));for (Document doc : collection.aggregate(pipeline)) {results.add(doc);}return results;}// 時間序列分析public List<Document> getUserRegistrationTrend() {List<Document> results = new ArrayList<>();List<Document> pipeline = Arrays.asList(new Document("$group", new Document("_id", new Document("year", new Document("$year", "$createdAt")).append("month", new Document("$month", "$createdAt"))).append("registrations", new Document("$sum", 1)).append("averageAge", new Document("$avg", "$age"))),new Document("$sort", new Document("_id.year", 1).append("_id.month", 1)),new Document("$project", new Document("_id", 0).append("period", new Document("$concat", Arrays.asList(new Document("$toString", "$_id.year"),"-",new Document("$toString", "$_id.month")))).append("registrations", 1).append("averageAge", new Document("$round", Arrays.asList("$averageAge", 1)))));for (Document doc : collection.aggregate(pipeline)) {results.add(doc);}return results;}// 復雜的多階段聚合public List<Document> getAdvancedUserAnalytics() {List<Document> results = new ArrayList<>();List<Document> pipeline = Arrays.asList(// 階段 1: 匹配活躍用戶new Document("$match", new Document("isActive", true)),// 階段 2: 添加計算字段new Document("$addFields", new Document("ageGroup", new Document("$switch", new Document("branches", Arrays.asList(new Document("case", new Document("$lt", Arrays.asList("$age", 25))).append("then", "18-24"),new Document("case", new Document("$lt", Arrays.asList("$age", 35))).append("then", "25-34"),new Document("case", new Document("$lt", Arrays.asList("$age", 50))).append("then", "35-49"))).append("default", "50+"))).append("emailDomain", new Document("$arrayElemAt", Arrays.asList(new Document("$split", Arrays.asList("$email", "@")), 1)))),// 階段 3: 按年齡組和郵箱域名分組new Document("$group", new Document("_id", new Document("ageGroup", "$ageGroup").append("emailDomain", "$emailDomain")).append("userCount", new Document("$sum", 1)).append("names", new Document("$push", "$name"))),// 階段 4: 重新分組以獲得每個年齡組的信息new Document("$group", new Document("_id", "$_id.ageGroup").append("totalUsers", new Document("$sum", "$userCount")).append("domains", new Document("$push", new Document("domain", "$_id.emailDomain").append("count", "$userCount")))),// 階段 5: 排序new Document("$sort", new Document("totalUsers", -1)),// 階段 6: 投影最終結果new Document("$project", new Document("_id", 0).append("ageGroup", "$_id").append("totalUsers", 1).append("topDomains", new Document("$slice", Arrays.asList(new Document("$sortArray", new Document("input", "$domains").append("sortBy", new Document("count", -1))), 3)))));for (Document doc : collection.aggregate(pipeline)) {results.add(doc);}return results;}
}
11. 事務處理
11.1 事務操作實現
package com.example.transaction;import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import org.bson.Document;
import org.bson.types.ObjectId;public class TransactionManager {private final MongoClient mongoClient;private final MongoDatabase database;public TransactionManager(MongoClient mongoClient, MongoDatabase database) {this.mongoClient = mongoClient;this.database = database;}// 簡單事務示例:轉賬操作public boolean transferMoney(ObjectId fromUserId, ObjectId toUserId, double amount) {ClientSession session = mongoClient.startSession();try {return session.withTransaction(() -> {MongoCollection<Document> accounts = database.getCollection("accounts");// 檢查轉出賬戶余額Document fromAccount = accounts.find(session, Filters.eq("_id", fromUserId)).first();if (fromAccount == null) {throw new RuntimeException("轉出賬戶不存在");}double fromBalance = fromAccount.getDouble("balance");if (fromBalance < amount) {throw new RuntimeException("余額不足");}// 檢查轉入賬戶是否存在Document toAccount = accounts.find(session, Filters.eq("_id", toUserId)).first();if (toAccount == null) {throw new RuntimeException("轉入賬戶不存在");}// 執行轉賬accounts.updateOne(session, Filters.eq("_id", fromUserId),Updates.inc("balance", -amount));accounts.updateOne(session,Filters.eq("_id", toUserId),Updates.inc("balance", amount));// 記錄交易日志MongoCollection<Document> transactions = database.getCollection("transactions");Document transaction = new Document().append("fromUserId", fromUserId).append("toUserId", toUserId).append("amount", amount).append("timestamp", System.currentTimeMillis()).append("status", "completed");transactions.insertOne(session, transaction);return true;});} catch (Exception e) {System.err.println("轉賬失敗: " + e.getMessage());return false;} finally {session.close();}}// 復雜事務示例:創建訂單public ObjectId createOrderWithInventoryUpdate(ObjectId userId, String productId, int quantity) {ClientSession session = mongoClient.startSession();try {return session.withTransaction(() -> {MongoCollection<Document> users = database.getCollection("users");MongoCollection<Document> products = database.getCollection("products");MongoCollection<Document> orders = database.getCollection("orders");MongoCollection<Document> inventory = database.getCollection("inventory");// 1. 驗證用戶存在Document user = users.find(session, Filters.eq("_id", userId)).first();if (user == null) {throw new RuntimeException("用戶不存在");}// 2. 驗證產品存在并獲取價格Document product = products.find(session, Filters.eq("productId", productId)).first();if (product == null) {throw new RuntimeException("產品不存在");}double price = product.getDouble("price");// 3. 檢查庫存并更新Document inventoryDoc = inventory.find(session, Filters.eq("productId", productId)).first();if (inventoryDoc == null) {throw new RuntimeException("庫存記錄不存在");}int currentStock = inventoryDoc.getInteger("quantity");if (currentStock < quantity) {throw new RuntimeException("庫存不足");}// 更新庫存inventory.updateOne(session,Filters.eq("productId", productId),Updates.inc("quantity", -quantity));// 4. 創建訂單ObjectId orderId = new ObjectId();Document order = new Document().append("_id", orderId).append("userId", userId).append("productId", productId).append("quantity", quantity).append("unitPrice", price).append("totalAmount", price * quantity).append("status", "pending").append("createdAt", System.currentTimeMillis());orders.insertOne(session, order);// 5. 更新用戶訂單歷史users.updateOne(session,Filters.eq("_id", userId),Updates.push("orderHistory", orderId));return orderId;});} catch (Exception e) {System.err.println("創建訂單失敗: " + e.getMessage());return null;} finally {session.close();}}// 回調式事務處理public boolean performMultiCollectionOperation() {ClientSession session = mongoClient.startSession();try {session.withTransaction(() -> {MongoCollection<Document> collection1 = database.getCollection("collection1");MongoCollection<Document> collection2 = database.getCollection("collection2");// 在事務中執行多個操作collection1.insertOne(session, new Document("field1", "value1"));collection2.updateOne(session, Filters.eq("_id", "someId"),Updates.set("field2", "value2"));// 如果這里拋出異常,整個事務會回滾// throw new RuntimeException("模擬失敗");return "success";});return true;} catch (Exception e) {System.err.println("事務執行失敗: " + e.getMessage());return false;} finally {session.close();}}// 手動事務控制public boolean manualTransactionControl() {ClientSession session = mongoClient.startSession();try {session.startTransaction();MongoCollection<Document> users = database.getCollection("users");MongoCollection<Document> logs = database.getCollection("logs");try {// 執行業務操作users.insertOne(session, new Document("name", "New User"));logs.insertOne(session, new Document("action", "user_created"));// 手動提交事務session.commitTransaction();return true;} catch (Exception e) {// 手動回滾事務session.abortTransaction();System.err.println("事務回滾: " + e.getMessage());return false;}} finally {session.close();}}
}
12. Spring Boot 集成
12.1 Spring Boot 配置
// pom.xml 添加依賴
/*
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
*/// application.yml
/*
spring:data:mongodb:uri: mongodb://localhost:27017/myapp# 或者分別配置host: localhostport: 27017database: myappusername: adminpassword: passwordauthentication-database: adminserver:port: 8080logging:level:org.springframework.data.mongodb: DEBUG
*/package com.example.config;import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;@Configuration
@EnableMongoRepositories(basePackages = "com.example.repository")
public class MongoConfig extends AbstractMongoClientConfiguration {@Overrideprotected String getDatabaseName() {return "myapp";}// 自定義配置/*@Overridepublic MongoClient mongoClient() {ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/myapp");MongoClientSettings mongoClientSettings = MongoClientSettings.builder().applyConnectionString(connectionString).build();return MongoClients.create(mongoClientSettings);}*/
}
12.2 Spring Data MongoDB 實體類
package com.example.entity;import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.index.Indexed;import java.time.LocalDateTime;
import java.util.List;@Document(collection = "users")
public class User {@Idprivate String id;@Field("full_name")private String name;@Indexed(unique = true)private String email;private Integer age;private Address address;private List<String> tags;@CreatedDateprivate LocalDateTime createdAt;@LastModifiedDateprivate LocalDateTime updatedAt;// 構造函數public User() {}public User(String name, String email, Integer age) {this.name = name;this.email = email;this.age = age;}// Getter 和 Setter 方法public String getId() { return id; }public void setId(String id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public String getEmail() { return email; }public void setEmail(String email) { this.email = email; }public Integer getAge() { return age; }public void setAge(Integer age) { this.age = age; }public Address getAddress() { return address; }public void setAddress(Address address) { this.address = address; }public List<String> getTags() { return tags; }public void setTags(List<String> tags) { this.tags = tags; }public LocalDateTime getCreatedAt() { return createdAt; }public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }public LocalDateTime getUpdatedAt() { return updatedAt; }public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}@Document
class Address {private String street;private String city;private String zipcode;private String country;// 構造函數和 Getter/Setter 方法public Address() {}public Address(String street, String city, String zipcode, String country) {this.street = street;this.city = city;this.zipcode = zipcode;this.country = country;}// Getter 和 Setter 方法省略...
}
12.3 Spring Data MongoDB Repository
package com.example.repository;import com.example.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.stereotype.Repository;import java.util.List;
import java.util.Optional;@Repository
public interface UserRepository extends MongoRepository<User, String> {// 基本查詢方法(Spring Data 自動實現)Optional<User> findByEmail(String email);List<User> findByName(String name);List<User> findByAgeGreaterThan(Integer age);List<User> findByAgeBetween(Integer minAge, Integer maxAge);List<User> findByNameContainingIgnoreCase(String keyword);List<User> findByAddressCity(String city);List<User> findByTagsContaining(String tag);// 分頁查詢Page<User> findByAgeGreaterThan(Integer age, Pageable pageable);// 排序查詢List<User> findByOrderByAgeDesc();List<User> findByAgeBetweenOrderByNameAsc(Integer minAge, Integer maxAge);// 自定義查詢@Query("{ 'age' : { $gte: ?0, $lte: ?1 } }")List<User> findUsersInAgeRange(Integer minAge, Integer maxAge);@Query("{ 'email' : { $regex: ?0, $options: 'i' } }")List<User> findByEmailPattern(String pattern);@Query(value = "{ 'age' : { $gte: ?0 } }", fields = "{ 'name' : 1, 'email' : 1 }")List<User> findUserNamesAndEmailsByMinAge(Integer minAge);// 刪除查詢Long deleteByAge(Integer age);Long deleteByAgeGreaterThan(Integer age);// 聚合查詢@Aggregation(pipeline = {"{ $group: { _id: null, totalUsers: { $sum: 1 }, averageAge: { $avg: '$age' } } }"})UserStatistics getUserStatistics();@Aggregation(pipeline = {"{ $group: { _id: '$address.city', count: { $sum: 1 } } }","{ $sort: { count: -1 } }"})List<CityUserCount> getUserCountByCity();// 統計接口interface UserStatistics {Integer getTotalUsers();Double getAverageAge();}interface CityUserCount {String getId(); // city nameInteger getCount();}
}
12.4 Service 層(Spring Boot)
package com.example.service;import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.Optional;@Service
@Transactional
public class UserService {@Autowiredprivate UserRepository userRepository;// 創建用戶public User createUser(User user) {// 業務邏輯驗證validateUser(user);// 檢查郵箱是否已存在Optional<User> existingUser = userRepository.findByEmail(user.getEmail());if (existingUser.isPresent()) {throw new IllegalArgumentException("Email already exists");}return userRepository.save(user);}// 批量創建用戶public List<User> createUsers(List<User> users) {// 驗證所有用戶users.forEach(this::validateUser);return userRepository.saveAll(users);}// 獲取用戶public Optional<User> getUserById(String id) {return userRepository.findById(id);}public Optional<User> getUserByEmail(String email) {return userRepository.findByEmail(email);}public List<User> getAllUsers() {return userRepository.findAll();}// 分頁查詢public Page<User> getUsersWithPagination(int page, int size, String sortBy, String sortDir) {Sort sort = sortDir.equalsIgnoreCase("desc") ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending();Pageable pageable = PageRequest.of(page, size, sort);return userRepository.findAll(pageable);}// 條件查詢public List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {return userRepository.findByAgeBetween(minAge, maxAge);}public List<User> searchUsersByName(String keyword) {return userRepository.findByNameContainingIgnoreCase(keyword);}public List<User> getUsersByCity(String city) {return userRepository.findByAddressCity(city);}public List<User> getUsersByTag(String tag) {return userRepository.findByTagsContaining(tag);}// 更新用戶public User updateUser(String id, User updatedUser) {return userRepository.findById(id).map(existingUser -> {existingUser.setName(updatedUser.getName());existingUser.setEmail(updatedUser.getEmail());existingUser.setAge(updatedUser.getAge());existingUser.setAddress(updatedUser.getAddress());existingUser.setTags(updatedUser.getTags());return userRepository.save(existingUser);}).orElseThrow(() -> new IllegalArgumentException("User not found with id: " + id));}// 刪除用戶public boolean deleteUser(String id) {return userRepository.findById(id).map(user -> {userRepository.delete(user);return true;}).orElse(false);}public void deleteAllUsers() {userRepository.deleteAll();}// 統計操作public long getUserCount() {return userRepository.count();}public UserRepository.UserStatistics getUserStatistics() {return userRepository.getUserStatistics();}public List<UserRepository.CityUserCount> getUserCountByCity() {return userRepository.getUserCountByCity();}// 業務邏輯驗證private void validateUser(User user) {if (user.getName() == null || user.getName().trim().isEmpty()) {throw new IllegalArgumentException("Name cannot be empty");}if (user.getEmail() == null || !isValidEmail(user.getEmail())) {throw new IllegalArgumentException("Invalid email format");}if (user.getAge() != null && (user.getAge() < 0 || user.getAge() > 150)) {throw new IllegalArgumentException("Age must be between 0 and 150");}}private boolean isValidEmail(String email) {return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");}
}
12.5 Controller 層
package com.example.controller;import com.example.entity.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;
import java.util.List;
import java.util.Optional;@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {@Autowiredprivate UserService userService;// 創建用戶@PostMappingpublic ResponseEntity<User> createUser(@Valid @RequestBody User user) {try {User createdUser = userService.createUser(user);return new ResponseEntity<>(createdUser, HttpStatus.CREATED);} catch (IllegalArgumentException e) {return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);}}// 批量創建用戶@PostMapping("/batch")public ResponseEntity<List<User>> createUsers(@Valid @RequestBody List<User> users) {try {List<User> createdUsers = userService.createUsers(users);return new ResponseEntity<>(createdUsers, HttpStatus.CREATED);} catch (IllegalArgumentException e) {return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);}}// 獲取所有用戶@GetMappingpublic ResponseEntity<List<User>> getAllUsers() {List<User> users = userService.getAllUsers();return new ResponseEntity<>(users, HttpStatus.OK);}// 分頁獲取用戶@GetMapping("/page")public ResponseEntity<Page<User>> getUsersWithPagination(@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "10") int size,@RequestParam(defaultValue = "id") String sortBy,@RequestParam(defaultValue = "asc") String sortDir) {Page<User> users = userService.getUsersWithPagination(page, size, sortBy, sortDir);return new ResponseEntity<>(users, HttpStatus.OK);}// 根據ID獲取用戶@GetMapping("/{id}")public ResponseEntity<User> getUserById(@PathVariable String id) {Optional<User> user = userService.getUserById(id);return user.map(u -> new ResponseEntity<>(u, HttpStatus.OK)).orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));}// 根據郵箱獲取用戶@GetMapping("/email/{email}")public ResponseEntity<User> getUserByEmail(@PathVariable String email) {Optional<User> user = userService.getUserByEmail(email);return user.map(u -> new ResponseEntity<>(u, HttpStatus.OK)).orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));}// 條件查詢@GetMapping("/age-range")public ResponseEntity<List<User>> getUsersByAgeRange(@RequestParam Integer minAge,@RequestParam Integer maxAge) {List<User> users = userService.getUsersByAgeRange(minAge, maxAge);return new ResponseEntity<>(users, HttpStatus.OK);}@GetMapping("/search")public ResponseEntity<List<User>> searchUsers(@RequestParam String keyword) {List<User> users = userService.searchUsersByName(keyword);return new ResponseEntity<>(users, HttpStatus.OK);}@GetMapping("/city/{city}")public ResponseEntity<List<User>> getUsersByCity(@PathVariable String city) {List<User> users = userService.getUsersByCity(city);return new ResponseEntity<>(users, HttpStatus.OK);}@GetMapping("/tag/{tag}")public ResponseEntity<List<User>> getUsersByTag(@PathVariable String tag) {List<User> users = userService.getUsersByTag(tag);return new ResponseEntity<>(users, HttpStatus.OK);}// 更新用戶@PutMapping("/{id}")public ResponseEntity<User> updateUser(@PathVariable String id, @Valid @RequestBody User user) {try {User updatedUser = userService.updateUser(id, user);return new ResponseEntity<>(updatedUser, HttpStatus.OK);} catch (IllegalArgumentException e) {return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);}}// 刪除用戶@DeleteMapping("/{id}")public ResponseEntity<Void> deleteUser(@PathVariable String id) {boolean deleted = userService.deleteUser(id);return deleted ? new ResponseEntity<>(HttpStatus.NO_CONTENT) : new ResponseEntity<>(HttpStatus.NOT_FOUND);}// 統計信息@GetMapping("/count")public ResponseEntity<Long> getUserCount() {long count = userService.getUserCount();return new ResponseEntity<>(count, HttpStatus.OK);}@GetMapping("/statistics")public ResponseEntity<UserService.UserRepository.UserStatistics> getUserStatistics() {UserService.UserRepository.UserStatistics stats = userService.getUserStatistics();return new ResponseEntity<>(stats, HttpStatus.OK);}@GetMapping("/city-stats")public ResponseEntity<List<UserService.UserRepository.CityUserCount>> getCityStats() {List<UserService.UserRepository.CityUserCount> stats = userService.getUserCountByCity();return new ResponseEntity<>(stats, HttpStatus.OK);}
}
13. 最佳實踐
13.1 性能優化
package com.example.optimization;import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import org.bson.Document;public class PerformanceOptimization {private final MongoCollection<Document> collection;public PerformanceOptimization(MongoCollection<Document> collection) {this.collection = collection;}// 1. 創建合適的索引public void createOptimizedIndexes() {// 單字段索引collection.createIndex(Indexes.ascending("email"));collection.createIndex(Indexes.ascending("age"));// 復合索引 - 注意字段順序collection.createIndex(Indexes.compoundIndex(Indexes.ascending("age"), // 選擇性高的字段放前面Indexes.ascending("city"),Indexes.descending("createdAt")));// 文本索引collection.createIndex(Indexes.text("name"));// 部分索引 - 只索引滿足條件的文檔IndexOptions partialOptions = new IndexOptions().partialFilterExpression(new Document("age", new Document("$gte", 18)));collection.createIndex(Indexes.ascending("email"), partialOptions);}// 2. 查詢優化示例public void optimizedQueries() {// 使用投影減少網絡傳輸collection.find().projection(new Document("name", 1).append("email", 1)).forEach(doc -> System.out.println(doc.toJson()));// 使用 limit 限制結果集collection.find().limit(10).forEach(doc -> System.out.println(doc.toJson()));// 使用 hint 強制使用特定索引collection.find(new Document("age", new Document("$gte", 18))).hint(new Document("age", 1)).forEach(doc -> System.out.println(doc.toJson()));}// 3. 批量操作優化public void optimizedBulkOperations() {// 使用批量寫入而不是多次單獨寫入// ... 批量操作代碼}
}
13.2 錯誤處理和日志
package com.example.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;@ControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);@ExceptionHandler(IllegalArgumentException.class)public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException e) {logger.warn("Invalid argument: {}", e.getMessage());return new ResponseEntity<>(new ErrorResponse("INVALID_ARGUMENT", e.getMessage()),HttpStatus.BAD_REQUEST);}@ExceptionHandler(RuntimeException.class)public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e) {logger.error("Runtime exception occurred", e);return new ResponseEntity<>(new ErrorResponse("INTERNAL_ERROR", "An internal error occurred"),HttpStatus.INTERNAL_SERVER_ERROR);}public static class ErrorResponse {private String code;private String message;public ErrorResponse(String code, String message) {this.code = code;this.message = message;}// Getter 方法public String getCode() { return code; }public String getMessage() { return message; }}
}
13.3 主應用程序類
package com.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;@SpringBootApplication
@EnableMongoAuditing
public class MongoDBApplication {public static void main(String[] args) {SpringApplication.run(MongoDBApplication.class, args);System.out.println("MongoDB Spring Boot Application Started!");}
}
13.4 測試類示例
package com.example;import com.example.entity.User;
import com.example.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
@TestPropertySource(properties = {"spring.data.mongodb.database=test_db"
})
public class UserServiceTest {@Autowiredprivate UserService userService;@Testpublic void testCreateUser() {User user = new User("Test User", "test@example.com", 25);User createdUser = userService.createUser(user);assertNotNull(createdUser.getId());assertEquals("Test User", createdUser.getName());assertEquals("test@example.com", createdUser.getEmail());assertEquals(25, createdUser.getAge());}@Testpublic void testFindUserByEmail() {// 先創建一個用戶User user = new User("Find Test", "findtest@example.com", 30);userService.createUser(user);// 然后查找var foundUser = userService.getUserByEmail("findtest@example.com");assertTrue(foundUser.isPresent());assertEquals("Find Test", foundUser.get().getName());}
}
這個完整指南涵蓋了 MongoDB 與 Java 集成的所有重要方面,從基礎安裝配置到高級的聚合操作和 Spring Boot 集成。每個部分都包含詳細的代碼示例和解釋,可以作為開發過程中的參考手冊。