MongoDB Java:查询匹配文档并过滤嵌套players数组字段
Got it, let's work through this problem. You need to fetch documents where at least one player in the players array has a specific nick, and then trim down that array to only include the matching player object. Here are two solid ways to do this with MongoDB's Java tools:
Using MongoDB Core Java Driver
This approach uses MongoDB's aggregation pipeline, which is flexible and works well even if multiple players in the array match the target nick (it'll keep all matching entries).
First, make sure you have the MongoDB Java driver dependency set up. Then use this code:
import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.Filters; import com.mongodb.client.model.Projections; import org.bson.Document; import java.util.Arrays; public class FilterNestedPlayerArray { public static void main(String[] args) { // Replace with your MongoDB connection string and database/collection names String connectionString = "mongodb://localhost:27017"; String dbName = "your_database"; String collectionName = "tournaments"; String targetNick = "player1"; // Auto-close the client with try-with-resources try (var mongoClient = MongoClients.create(connectionString)) { MongoDatabase database = mongoClient.getDatabase(dbName); MongoCollection<Document> collection = database.getCollection(collectionName); // Build the aggregation pipeline var pipeline = Arrays.asList( // Step 1: Filter documents that have at least one player with the target nick Aggregates.match(Filters.eq("players.nick", targetNick)), // Step 2: Project all original fields, but filter the players array to only matches Aggregates.project(Projections.fields( // Include all top-level fields you want to keep Projections.include("_id", "type", "prizepool", "name", "start", "end", "buyin", "rake", "prize", "bounty"), // Use $filter to trim the players array Projections.computed("players", new Document("$filter", new Document("input", "$players") .append("as", "player") .append("cond", new Document("$eq", Arrays.asList("$$player.nick", targetNick))) ) ) )) ); // Execute the query and print results collection.aggregate(pipeline).forEach(resultDoc -> { System.out.println(resultDoc.toJson()); }); } } }
Breakdown of the Pipeline:
$matchStage: First narrows down the documents to only those that contain the targetnickin theplayersarray. This reduces the data we need to process in the next step.$projectStage:Projections.includeensures we keep all the top-level fields from the original document (adjust this list if you need to add/remove fields).$filteriterates over theplayersarray, checking each element'snickagainst our target. Only matching elements stay in the array.
Using Spring Data MongoDB (For Spring Projects)
If you're using Spring Data MongoDB, you can leverage its fluent API to build the same aggregation logic more cleanly:
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Component; // Assume this is a Spring component with MongoTemplate injected @Component public class TournamentRepositoryHelper { private final MongoTemplate mongoTemplate; public TournamentRepositoryHelper(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } public Tournament getTournamentWithMatchingPlayer(String targetNick) { // Step 1: Match documents with the target nick in players array AggregationOperation matchStage = Aggregation.match(Criteria.where("players.nick").is(targetNick)); // Step 2: Project fields and filter the players array AggregationOperation projectStage = Aggregation.project() .andInclude("_id", "type", "prizepool", "name", "start", "end", "buyin", "rake", "prize", "bounty") .and(Aggregation.filter("players") .as("player") .by(Criteria.where("$$player.nick").is(targetNick))) .as("players"); // Build and execute the aggregation Aggregation aggregation = Aggregation.newAggregation(matchStage, projectStage); return mongoTemplate.aggregate(aggregation, "tournaments", Tournament.class).getUniqueMappedResult(); } } // Assume you have a Tournament POJO matching your document structure // public class Tournament { // private String id; // private int type; // private double prizepool; // // ... other fields // private List<Player> players; // // Getters and setters // } // // public class Player { // private String nick; // private int rank; // private double cash; // // ... other fields // // Getters and setters // }
Alternative: $elemMatch for Single Matches
If you're certain there will only ever be one player with the target nick per document, you can use $elemMatch in a projection for a simpler query (no aggregation needed):
// Fetch the first matching document with only the matching player in the array Document result = collection.find(Filters.eq("players.nick", targetNick)) .projection(Projections.fields( Projections.include("_id", "type", "prizepool", ...), // Keep top-level fields Projections.elemMatch("players", Filters.eq("nick", targetNick)) )) .first();
Note: This will only return the first matching player in the array if there are multiple entries with the same nick—so use this only when you know duplicates won't exist.
内容的提问来源于stack exchange,提问作者David B

