MongoDB QuickStart

验证:未记录
适用版本
未记录
验证范围
尚未登记验证记录。

本文缩写(英文全称与中文含义)
缩写 英文全称/名称 中文含义
API Application Programming Interface 应用程序编程接口
BSON Binary JSON MongoDB 使用的二进制文档格式
CLI Command-Line Interface 命令行接口
COLLSCAN Collection Scan MongoDB 全集合扫描
JSON JavaScript Object Notation 一种结构化数据格式
MB Megabyte 十进制兆字节,1 MB = 10⁶ 字节
QPS Queries Per Second 每秒查询数
URI Uniform Resource Identifier 统一资源标识符
https://www.mongodb.com/docs/languages/java/
https://www.mongodb.com/docs/drivers/java/sync/current/getting-started/quick-reference/
https://www.mongodb.com/docs/drivers/java/sync/current/data-formats/document-data-format-pojo/
https://www.mongodb.com/docs/manual/reference/connection-string/
https://www.mongodb.com/docs/manual/crud/

1. What You Need To Know#

MongoClient 应复用连接池,不要每个请求创建新 client;连接池上限要和应用副本数一起核算。

MongoDB concepts:
    database:
        类似 MySQL database

    collection:
        类似 table,但不要求每条 document schema 完全一致

    document:
        BSON document,类似 JSON object
        一条 document 最大 16 MB

    _id:
        每条 document 的 primary key
        不指定时 MongoDB 会自动生成 ObjectId

    index:
        query performance 的核心
        没有合适 index,高 QPS query 很容易变成 COLLSCAN

    replica set:
        production baseline
        primary handles writes
        secondary replicates from primary

    connection string:
        driver 通过 URI 连接 MongoDB
        production replica set 应该写多个 host + replicaSet
mental model:
    MongoClient
        -> MongoDatabase
            -> MongoCollection<Document>
                -> insertOne / find / updateOne / deleteOne

2. Hello World Goal#

we will build:
    Java CLI app
    connect to MongoDB
    create collection index
    insert one order
    query order by user_id
    update order status
    delete order

language:
    Java

driver:
    MongoDB Java Sync Driver

3. Maven Dependency#

<project>
  <modelVersion>4.0.0</modelVersion>

  <groupId>me.xiezhao.demo</groupId>
  <artifactId>mongodb-hello-world</artifactId>
  <version>1.0.0</version>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.mongodb</groupId>
      <artifactId>mongodb-driver-sync</artifactId>
      <version>5.5.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.5.0</version>
      </plugin>
    </plugins>
  </build>
</project>

4. Configuration#

local dev#

# application.properties
mongodb.uri=mongodb://root:change-me@localhost:27017/order?authSource=admin&appName=order-api
mongodb.database=order
mongodb.collection=orders

production replica set#

mongodb.uri=mongodb://order_app:<password>@mongo-1:27017,mongo-2:27017,mongo-3:27017/order?replicaSet=rs0&authSource=admin&appName=order-api&retryWrites=true&w=majority
mongodb.database=order
mongodb.collection=orders
important options:
    replicaSet=rs0
        tell driver this is a replica set

    authSource=admin
        user is created in admin database

    appName=order-api
        appears in MongoDB logs / profiler

    retryWrites=true
        driver can retry supported write operations

    w=majority
        write acknowledged by majority

5. Start MongoDB Locally#

docker run -d --name mongo -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=change-me mongo:8
mongosh "mongodb://root:change-me@localhost:27017/admin"

6. Project Structure#

mongodb-hello-world
├── pom.xml
└── src
    └── main
        ├── java
        │   └── me
        │       └── xiezhao
        │           └── demo
        │               └── MongoHelloWorld.java
        └── resources
            └── application.properties

7. Java Hello World#

package me.xiezhao.demo;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.Updates;
import org.bson.Document;
import org.bson.conversions.Bson;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Properties;

public class MongoHelloWorld {
    public static void main(String[] args) throws IOException {
        Properties properties = loadProperties();

        String uri = properties.getProperty("mongodb.uri");
        String databaseName = properties.getProperty("mongodb.database");
        String collectionName = properties.getProperty("mongodb.collection");

        try (MongoClient client = MongoClients.create(uri)) {
            MongoDatabase database = client.getDatabase(databaseName);
            MongoCollection<Document> orders = database.getCollection(collectionName);

            orders.createIndex(Indexes.compoundIndex(
                    Indexes.ascending("user_id"),
                    Indexes.descending("created_at")
            ));

            String orderId = "o-1001";
            String userId = "u-1001";

            Document order = new Document("order_id", orderId)
                    .append("user_id", userId)
                    .append("status", "PENDING")
                    .append("amount", new BigDecimal("99.90"))
                    .append("created_at", Instant.now());

            orders.insertOne(order);

            Bson queryByUser = Filters.eq("user_id", userId);
            Document found = orders.find(queryByUser)
                    .sort(Indexes.descending("created_at"))
                    .first();

            System.out.println("found order: " + found.toJson());

            Bson queryByOrderId = Filters.eq("order_id", orderId);
            orders.updateOne(
                    queryByOrderId,
                    Updates.combine(
                            Updates.set("status", "PAID"),
                            Updates.set("paid_at", Instant.now())
                    )
            );

            Document paidOrder = orders.find(queryByOrderId).first();
            System.out.println("paid order: " + paidOrder.toJson());

            orders.deleteOne(queryByOrderId);
        }
    }

    private static Properties loadProperties() throws IOException {
        Properties properties = new Properties();

        try (InputStream input = MongoHelloWorld.class
                .getClassLoader()
                .getResourceAsStream("application.properties")) {
            if (input == null) {
                throw new IllegalStateException("application.properties not found");
            }
            properties.load(input);
        }

        return properties;
    }
}

8. Run#

mvn -q compile exec:java -Dexec.mainClass=me.xiezhao.demo.MongoHelloWorld
expected:
    found order: {"_id": ..., "order_id": "o-1001", ...}
    paid order: {"_id": ..., "order_id": "o-1001", "status": "PAID", ...}

9. How To Use In Real Project#

client lifecycle#

rule:
    create MongoClient once when application starts
    reuse it for all requests
    close it when application stops

do not:
    create MongoClient per request
public final class MongoProvider implements AutoCloseable {
    private final MongoClient client;
    private final MongoDatabase database;

    public MongoProvider(String uri, String databaseName) {
        this.client = MongoClients.create(uri);
        this.database = client.getDatabase(databaseName);
    }

    public MongoCollection<Document> collection(String name) {
        return database.getCollection(name);
    }

    @Override
    public void close() {
        client.close();
    }
}

repository pattern#

public final class OrderRepository {
    private final MongoCollection<Document> orders;

    public OrderRepository(MongoCollection<Document> orders) {
        this.orders = orders;
    }

    public void createOrder(String orderId, String userId, BigDecimal amount) {
        Document order = new Document("order_id", orderId)
                .append("user_id", userId)
                .append("status", "PENDING")
                .append("amount", amount)
                .append("created_at", Instant.now());

        orders.insertOne(order);
    }

    public Document findByOrderId(String orderId) {
        return orders.find(Filters.eq("order_id", orderId)).first();
    }

    public void markPaid(String orderId) {
        orders.updateOne(
                Filters.and(
                        Filters.eq("order_id", orderId),
                        Filters.eq("status", "PENDING")
                ),
                Updates.combine(
                        Updates.set("status", "PAID"),
                        Updates.set("paid_at", Instant.now())
                )
        );
    }
}
repository should own:
    collection name
    query condition
    update expression
    index requirement

service should own:
    business workflow
    validation
    transaction decision

10. Production Checklist#

before coding:
    write access patterns first
    design document shape
    design indexes
    decide consistency requirement

application config:
    URI comes from env / secret manager
    appName is set
    replicaSet is set for replica set
    retryWrites is enabled when suitable
    write concern is explicit for critical data

code:
    reuse MongoClient
    do not create client per request
    set query limit
    use projection for large documents
    avoid skip for deep pagination
    use update operators, not full document replacement by default

operation:
    create index before production traffic
    review explain for hot queries
    monitor slow query
    monitor connection pool
    monitor replication lag

11. Common Mistakes#

mistakes:
    no index on hot query
    full collection scan in API path
    create MongoClient per request
    large skip pagination
    huge document / unbounded array
    read from secondary without considering lag
    use root user in application
    expose MongoDB to public internet
    no backup restore test

搜索文档

输入关键词,搜索所有文档。

↑ ↓ 选择 · Enter 打开 · Esc 关闭