MongoDB内嵌文档深度解析:使用MongoTemplate进行高效操作

一、MongoDB内嵌文档设计理念

1.1 什么是内嵌文档

内嵌文档(Embedded Documents)是MongoDB区别于传统关系型数据库的重要特性之一。在MongoDB中,一个文档可以包含另一个文档作为其字段的值,形成嵌套结构。

为什么选择内嵌文档?

  • 数据局部性:相关数据存储在一起,减少磁盘寻址时间
  • 原子性操作:支持对嵌套文档的原子操作
  • 查询性能:避免了复杂的关联查询
  • 灵活的模式:适应快速变化的业务需求

二、内嵌文档的实体设计

2.1 基础实体定义

java 复制代码
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.List;
import java.util.Map;

/**
 * 用户实体 - 展示多种内嵌文档形式
 */
@Data
@Document(collection = "users")
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class User {
    
    @Id
    private String userId;
    
    private String username;
    private String email;
    private Integer age;
    
    // 1. 简单的内嵌对象
    private Address address;
    
    // 2. 内嵌对象列表
    private List<Education> educationHistory;
    
    // 3. 内嵌Map结构
    private Map<String, Preference> preferences;
    
    // 4. 嵌套多层的内嵌文档
    private List<WorkExperience> workExperiences;
    
    // 5. 混合类型内嵌
    private ContactInfo contactInfo;
}

/**
 * 地址 - 简单的内嵌对象
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    @Field("addr_line1")
    private String line1;
    
    @Field("addr_line2")
    private String line2;
    
    private String city;
    private String state;
    private String postalCode;
    private String country;
    
    // 坐标 - 内嵌对象中的内嵌对象
    private GeoLocation location;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class GeoLocation {
        private Double latitude;
        private Double longitude;
        private String type = "Point";
    }
}

/**
 * 教育经历 - 列表形式的内嵌文档
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Education {
    private String institution;
    private String degree;
    private String fieldOfStudy;
    
    @Field("start_date")
    private Date startDate;
    
    @Field("end_date")
    private Date endDate;
    
    // 成绩单 - 内嵌文档列表
    private List<CourseGrade> grades;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class CourseGrade {
        private String courseCode;
        private String courseName;
        private String grade;
        private Integer credits;
    }
}

/**
 * 工作经历 - 多层嵌套的内嵌文档
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WorkExperience {
    private String company;
    private String position;
    private Date startDate;
    private Date endDate;
    private String description;
    
    // 项目经历 - 内嵌文档列表
    private List<Project> projects;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Project {
        private String projectName;
        private String role;
        private List<String> technologies;
        
        // 项目成果 - 内嵌文档列表
        private List<Achievement> achievements;
        
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public static class Achievement {
            private String title;
            private String description;
            private Integer impactScore;
        }
    }
}

/**
 * 联系信息 - 混合类型内嵌
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ContactInfo {
    // 简单字段
    private String phone;
    private String wechat;
    
    // 内嵌对象
    private EmergencyContact emergencyContact;
    
    // 内嵌Map
    private Map<String, SocialMediaAccount> socialMedia;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class EmergencyContact {
        private String name;
        private String relationship;
        private String phone;
        private Integer priority;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SocialMediaAccount {
        private String username;
        private String url;
        private Boolean isVerified;
    }
}

/**
 * 用户偏好 - Map形式的内嵌
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Preference {
    private String value;
    private Date lastUpdated;
    private Integer priority;
}

三、内嵌文档的CRUD操作

3.1 插入操作

java 复制代码
@Service
@Slf4j
public class EmbeddedDocumentService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 插入包含复杂内嵌文档的用户
     */
    public User createUserWithEmbeddedDocs() {
        User user = new User()
            .setUserId("U1001")
            .setUsername("zhangsan")
            .setEmail("zhangsan@example.com")
            .setAge(28);
        
        // 设置地址(简单内嵌对象)
        Address address = new Address();
        address.setLine1("123 Main St");
        address.setCity("Beijing");
        address.setCountry("China");
        address.setLocation(new Address.GeoLocation(39.9042, 116.4074));
        user.setAddress(address);
        
        // 设置教育经历(内嵌对象列表)
        List<Education> educations = new ArrayList<>();
        
        Education bachelor = new Education();
        bachelor.setInstitution("Tsinghua University");
        bachelor.setDegree("Bachelor");
        bachelor.setFieldOfStudy("Computer Science");
        
        List<Education.CourseGrade> grades = new ArrayList<>();
        grades.add(new Education.CourseGrade("CS101", "Programming", "A", 3));
        grades.add(new Education.CourseGrade("CS201", "Data Structures", "A-", 4));
        bachelor.setGrades(grades);
        
        educations.add(bachelor);
        user.setEducationHistory(educations);
        
        // 设置工作经历(多层嵌套)
        List<WorkExperience> experiences = new ArrayList<>();
        WorkExperience exp = new WorkExperience();
        exp.setCompany("Tech Corp");
        exp.setPosition("Senior Developer");
        
        List<WorkExperience.Project> projects = new ArrayList<>();
        WorkExperience.Project project = new WorkExperience.Project();
        project.setProjectName("E-commerce Platform");
        project.setRole("Backend Lead");
        project.setTechnologies(Arrays.asList("Java", "Spring", "MongoDB"));
        
        List<WorkExperience.Project.Achievement> achievements = new ArrayList<>();
        achievements.add(new WorkExperience.Project.Achievement(
            "Performance Optimization", 
            "Improved API response time by 60%",
            8
        ));
        project.setAchievements(achievements);
        projects.add(project);
        exp.setProjects(projects);
        
        experiences.add(exp);
        user.setWorkExperiences(experiences);
        
        // 插入到数据库
        User savedUser = mongoTemplate.insert(user);
        log.info("Created user with embedded documents: {}", savedUser.getUserId());
        
        return savedUser;
    }
    
    /**
     * 批量插入内嵌文档
     */
    public List<User> batchInsertUsers(List<User> users) {
        return mongoTemplate.insertAll(users);
    }
}

3.2 查询操作

java 复制代码
@Service
public class EmbeddedQueryService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 1. 查询简单内嵌文档字段
     * 查询地址在北京的用户
     */
    public List<User> findUsersByCity(String city) {
        Query query = Query.query(
            Criteria.where("address.city").is(city)
        );
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 2. 查询内嵌文档的嵌套字段
     * 查询坐标在特定范围内的用户
     */
    public List<User> findUsersNearLocation(Double lat, Double lon, Double maxDistanceInKm) {
        Query query = Query.query(
            Criteria.where("address.location")
                .near(new Point(lon, lat))
                .maxDistance(maxDistanceInKm / 111.12) // 转换为度
        );
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 3. 查询内嵌数组中的元素
     * 查询有特定教育经历的用户
     */
    public List<User> findUsersByEducation(String institution, String degree) {
        Query query = Query.query(
            Criteria.where("educationHistory")
                .elemMatch(Criteria
                    .where("institution").is(institution)
                    .and("degree").is(degree)
                )
        );
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 4. 多层嵌套查询
     * 查询使用过特定技术的用户
     */
    public List<User> findUsersByTechnology(String technology) {
        Query query = Query.query(
            Criteria.where("workExperiences.projects.technologies")
                .is(technology)
        );
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 5. 复杂条件组合查询
     * 查询在北京且拥有硕士学位的用户
     */
    public List<User> findUsersByComplexCriteria() {
        Criteria criteria = new Criteria().andOperator(
            Criteria.where("address.city").is("Beijing"),
            Criteria.where("educationHistory")
                .elemMatch(Criteria.where("degree").is("Master"))
        );
        
        Query query = Query.query(criteria);
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 6. 查询内嵌Map中的值
     */
    public List<User> findUsersByPreference(String prefKey, String prefValue) {
        Query query = Query.query(
            Criteria.where("preferences." + prefKey + ".value")
                .is(prefValue)
        );
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 7. 使用投影返回部分内嵌字段
     */
    public List<User> findUsersWithSelectedFields() {
        Query query = new Query();
        query.fields()
            .include("username")
            .include("email")
            .include("address.city")
            .include("address.location")
            .include("educationHistory.institution")
            .include("educationHistory.degree");
        
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 8. 聚合查询内嵌文档
     */
    public List<Document> aggregateEmbeddedDocuments() {
        Aggregation aggregation = Aggregation.newAggregation(
            // 展开教育经历数组
            Aggregation.unwind("educationHistory"),
            
            // 按学位分组统计
            Aggregation.group("educationHistory.degree")
                .count().as("count")
                .avg("age").as("avgAge"),
                
            // 按数量排序
            Aggregation.sort(Sort.Direction.DESC, "count"),
            
            // 投影
            Aggregation.project()
                .and("_id").as("degree")
                .and("count").as("userCount")
                .and("avgAge").as("averageAge")
                .andExclude("_id")
        );
        
        return mongoTemplate.aggregate(aggregation, User.class, Document.class)
            .getMappedResults();
    }
}

3.3 更新操作

java 复制代码
@Service
@Slf4j
public class EmbeddedUpdateService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 1. 更新简单内嵌文档字段
     */
    public void updateUserAddress(String userId, String newCity) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = Update.update("address.city", newCity);
        
        UpdateResult result = mongoTemplate.updateFirst(query, update, User.class);
        log.info("Updated address for user {}: {}", userId, result.getModifiedCount());
    }
    
    /**
     * 2. 替换整个内嵌文档
     */
    public void replaceAddress(String userId, Address newAddress) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = Update.update("address", newAddress);
        
        mongoTemplate.updateFirst(query, update, User.class);
    }
    
    /**
     * 3. 向内嵌数组添加元素
     */
    public void addEducationRecord(String userId, Education education) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = new Update().push("educationHistory", education);
        
        mongoTemplate.updateFirst(query, update, User.class);
    }
    
    /**
     * 4. 从内嵌数组移除元素
     */
    public void removeEducationRecord(String userId, String institution) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = new Update().pull("educationHistory", 
            Query.query(Criteria.where("institution").is(institution)));
        
        mongoTemplate.updateFirst(query, update, User.class);
    }
    
    /**
     * 5. 更新内嵌数组中的特定元素
     * 使用$[<identifier>]和arrayFilters
     */
    public void updateSpecificEducation(String userId, String oldInstitution, String newInstitution) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        
        Update update = new Update()
            .set("educationHistory.$[elem].institution", newInstitution)
            .set("educationHistory.$[elem].lastUpdated", new Date());
        
        // 定义数组过滤器
        ArrayFilter filter = ArrayFilter.filter(Criteria.where("elem.institution").is(oldInstitution));
        
        UpdateOptions options = new UpdateOptions().arrayFilters(Arrays.asList(filter));
        
        mongoTemplate.updateFirst(query, update, options, User.class);
    }
    
    /**
     * 6. 更新多层嵌套的内嵌文档
     */
    public void updateNestedProject(String userId, String company, String projectName, String newRole) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        
        // 构建复杂的更新路径
        String updatePath = "workExperiences.$[exp].projects.$[proj].role";
        
        Update update = new Update().set(updatePath, newRole);
        
        // 定义多个数组过滤器
        List<ArrayFilter> filters = Arrays.asList(
            ArrayFilter.filter(Criteria.where("exp.company").is(company)),
            ArrayFilter.filter(Criteria.where("proj.projectName").is(projectName))
        );
        
        UpdateOptions options = new UpdateOptions().arrayFilters(filters);
        
        mongoTemplate.updateFirst(query, update, options, User.class);
    }
    
    /**
     * 7. 使用$addToSet避免重复
     */
    public void addUniqueTechnology(String userId, String company, String projectName, String technology) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        
        Update update = new Update()
            .addToSet("workExperiences.$[exp].projects.$[proj].technologies", technology);
        
        List<ArrayFilter> filters = Arrays.asList(
            ArrayFilter.filter(Criteria.where("exp.company").is(company)),
            ArrayFilter.filter(Criteria.where("proj.projectName").is(projectName))
        );
        
        UpdateOptions options = new UpdateOptions().arrayFilters(filters);
        
        mongoTemplate.updateFirst(query, update, options, User.class);
    }
    
    /**
     * 8. 批量更新内嵌文档
     */
    public void batchUpdateEmbeddedFields(List<String> userIds, String fieldPath, Object value) {
        Query query = Query.query(Criteria.where("userId").in(userIds));
        Update update = Update.update(fieldPath, value);
        
        UpdateResult result = mongoTemplate.updateMulti(query, update, User.class);
        log.info("Batch updated {} documents", result.getModifiedCount());
    }
}

3.4 删除操作

java 复制代码
@Service
@Slf4j
public class EmbeddedDeleteService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 1. 删除特定内嵌字段
     */
    public void removeAddressField(String userId) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = new Update().unset("address");
        
        mongoTemplate.updateFirst(query, update, User.class);
    }
    
    /**
     * 2. 删除内嵌数组中的特定元素
     */
    public void deleteSpecificEducation(String userId, String institution) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = new Update().pull("educationHistory", 
            Query.query(Criteria.where("institution").is(institution)));
        
        mongoTemplate.updateFirst(query, update, User.class);
    }
    
    /**
     * 3. 清空内嵌数组
     */
    public void clearEducationHistory(String userId) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = new Update().set("educationHistory", new ArrayList<>());
        
        mongoTemplate.updateFirst(query, update, User.class);
    }
    
    /**
     * 4. 删除符合条件的内嵌数组元素
     */
    public void deleteOldWorkExperiences(String userId, Date cutoffDate) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        
        // 使用$pull删除endDate早于cutoffDate的工作经历
        Update update = new Update().pull("workExperiences",
            Query.query(Criteria.where("endDate").lt(cutoffDate))
        );
        
        mongoTemplate.updateFirst(query, update, User.class);
    }
    
    /**
     * 5. 删除多层嵌套中的特定元素
     */
    public void deleteProjectAchievement(String userId, String company, String projectName, String achievementTitle) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        
        Update update = new Update()
            .pull("workExperiences.$[exp].projects.$[proj].achievements",
                Query.query(Criteria.where("title").is(achievementTitle))
            );
        
        List<ArrayFilter> filters = Arrays.asList(
            ArrayFilter.filter(Criteria.where("exp.company").is(company)),
            ArrayFilter.filter(Criteria.where("proj.projectName").is(projectName))
        );
        
        UpdateOptions options = new UpdateOptions().arrayFilters(filters);
        mongoTemplate.updateFirst(query, update, options, User.class);
    }
}

四、高级操作与最佳实践

4.1 使用FindAndModify原子操作

java 复制代码
@Service
public class EmbeddedAtomicService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 原子性地更新并返回文档
     */
    public User findAndUpdateEducation(String userId, Education newEducation) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        Update update = new Update().push("educationHistory", newEducation);
        
        return mongoTemplate.findAndModify(
            query, 
            update, 
            FindAndModifyOptions.options().returnNew(true), 
            User.class
        );
    }
    
    /**
     * 原子递增内嵌字段
     */
    public void incrementAchievementScore(String userId, String company, String projectName, String achievementTitle, int increment) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        
        String updatePath = "workExperiences.$[exp].projects.$[proj].achievements.$[ach].impactScore";
        
        Update update = new Update().inc(updatePath, increment);
        
        List<ArrayFilter> filters = Arrays.asList(
            ArrayFilter.filter(Criteria.where("exp.company").is(company)),
            ArrayFilter.filter(Criteria.where("proj.projectName").is(projectName)),
            ArrayFilter.filter(Criteria.where("ach.title").is(achievementTitle))
        );
        
        UpdateOptions options = new UpdateOptions().arrayFilters(filters);
        mongoTemplate.updateFirst(query, update, options, User.class);
    }
}

4.2 使用Bulk Operations批量操作

java 复制代码
@Service
@Slf4j
public class EmbeddedBulkService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 批量更新内嵌文档
     */
    public void bulkUpdateEmbeddedDocuments() {
        BulkOperations bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED, User.class);
        
        // 添加多个更新操作
        bulkOps.updateOne(
            Query.query(Criteria.where("userId").is("U1001")),
            Update.update("address.city", "Shanghai")
        );
        
        bulkOps.updateOne(
            Query.query(Criteria.where("userId").is("U1002")),
            new Update().push("educationHistory", 
                new Education("MIT", "PhD", "AI", new Date(), null, null))
        );
        
        // 执行批量操作
        BulkWriteResult result = bulkOps.execute();
        log.info("Bulk operations completed. Modified: {}", result.getModifiedCount());
    }
}

4.3 索引优化

java 复制代码
@Configuration
public class EmbeddedIndexConfig {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    @PostConstruct
    public void createEmbeddedIndexes() {
        // 为内嵌字段创建索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index().on("address.city", Sort.Direction.ASC)
        );
        
        // 为内嵌数组字段创建索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index().on("educationHistory.institution", Sort.Direction.ASC)
        );
        
        // 创建复合索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index()
                .on("address.city", Sort.Direction.ASC)
                .on("age", Sort.Direction.DESC)
        );
        
        // 为地理空间字段创建索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index().on("address.location", IndexDirection.GEO2DSPHERE)
        );
        
        // 为嵌套多层的内嵌字段创建索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index()
                .on("workExperiences.company", Sort.Direction.ASC)
                .on("workExperiences.projects.technologies", Sort.Direction.ASC)
        );
    }
}

五、性能优化策略

5.1 查询优化

java 复制代码
@Service
public class EmbeddedQueryOptimizationService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 使用投影减少返回数据量
     */
    public List<User> findUsersWithOptimizedProjection() {
        Query query = new Query();
        
        // 只返回需要的字段
        query.fields()
            .include("userId")
            .include("username")
            .include("address.city")
            .slice("educationHistory", 2)  // 只返回前2个教育经历
            .elemMatch("workExperiences", 
                Criteria.where("company").is("Google"))
            .include("workExperiences.$");
        
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 使用游标处理大量数据
     */
    public void processLargeEmbeddedDataset() {
        Query query = Query.query(Criteria.where("address.city").is("Beijing"));
        query.cursorBatchSize(100);  // 每批返回100条
        
        try (MongoCursor<User> cursor = mongoTemplate.stream(query, User.class)) {
            while (cursor.hasNext()) {
                User user = cursor.next();
                // 处理每个用户
                processUser(user);
            }
        }
    }
    
    private void processUser(User user) {
        // 处理逻辑
    }
}

5.2 设计模式建议

java 复制代码
/**
 * 内嵌文档设计模式示例
 */
public class EmbeddedDesignPatterns {
    
    /**
     * 模式1:子集模式 - 存储最常用的字段
     */
    @Document(collection = "products")
    @Data
    public static class Product {
        @Id
        private String id;
        private String name;
        private Double price;
        
        // 内嵌最常用的评论信息
        private List<ReviewSummary> topReviews;
        
        // 完整评论在另一个集合
        private String fullReviewsCollection = "product_reviews";
        
        @Data
        public static class ReviewSummary {
            private String userId;
            private Integer rating;
            private String summary;
        }
    }
    
    /**
     * 模式2:扩展引用模式 - 处理大文档
     */
    @Document(collection = "user_profiles")
    @Data
    public static class UserProfile {
        @Id
        private String userId;
        
        // 基本信息内嵌
        private BasicInfo basicInfo;
        
        // 扩展信息引用
        private List<ExtensionRef> extensions;
        
        @Data
        public static class BasicInfo {
            private String name;
            private String email;
            private String avatar;
        }
        
        @Data
        public static class ExtensionRef {
            private String type;  // "education", "work", "skills"
            private String refId; // 指向详细文档的ID
        }
    }
    
    /**
     * 模式3:近似模式 - 存储近似值
     */
    @Document(collection = "social_posts")
    @Data
    public static class SocialPost {
        @Id
        private String postId;
        private String content;
        
        // 近似计数器 - 避免频繁更新
        private EngagementCounts engagement;
        
        // 精确计数器在另一个地方维护
        private String exactCountsRef;
        
        @Data
        public static class EngagementCounts {
            private Integer likes;
            private Integer shares;
            private Integer comments;
            private Date lastUpdated;
        }
    }
}

六、常见问题与解决方案

6.1 内嵌文档大小限制

java 复制代码
@Service
public class EmbeddedSizeManagementService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 检查并优化文档大小
     */
    public void optimizeDocumentSize(String userId) {
        Query query = Query.query(Criteria.where("userId").is(userId));
        
        // 使用聚合检查文档大小
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("userId").is(userId)),
            Aggregation.project()
                .and("userId").as("userId")
                .and(BsonDocument.parse("{ $bsonSize: '$$ROOT' }")).as("sizeInBytes")
        );
        
        Document sizeInfo = mongoTemplate.aggregate(aggregation, User.class, Document.class)
            .getUniqueMappedResult();
        
        if (sizeInfo != null) {
            Long sizeInBytes = sizeInfo.getLong("sizeInBytes");
            if (sizeInBytes > 16 * 1024 * 1024) { // 超过16MB
                handleOversizedDocument(userId);
            }
        }
    }
    
    private void handleOversizedDocument(String userId) {
        // 处理过大的文档
        // 1. 存档旧数据
        // 2. 拆分文档
        // 3. 使用GridFS
    }
    
    /**
     * 自动分块内嵌数组
     */
    public void chunkLargeArray(String userId, String arrayField, int chunkSize) {
        User user = mongoTemplate.findById(userId, User.class);
        
        if (user != null) {
            try {
                // 获取数组值
                List<?> array = (List<?>) BeanUtils.getProperty(user, arrayField);
                
                if (array.size() > chunkSize) {
                    // 拆分数组
                    List<List<?>> chunks = partition(array, chunkSize);
                    
                    // 保存主文档(只保留第一块)
                    BeanUtils.setProperty(user, arrayField, chunks.get(0));
                    mongoTemplate.save(user);
                    
                    // 保存剩余块到新文档
                    for (int i = 1; i < chunks.size(); i++) {
                        User chunkDoc = new User();
                        chunkDoc.setUserId(userId + "_chunk_" + i);
                        BeanUtils.setProperty(chunkDoc, arrayField, chunks.get(i));
                        mongoTemplate.save(chunkDoc, "user_chunks");
                    }
                }
            } catch (Exception e) {
                log.error("Failed to chunk array", e);
            }
        }
    }
    
    private <T> List<List<T>> partition(List<T> list, int size) {
        List<List<T>> partitions = new ArrayList<>();
        for (int i = 0; i < list.size(); i += size) {
            partitions.add(list.subList(i, Math.min(i + size, list.size())));
        }
        return partitions;
    }
}

6.2 事务支持

java 复制代码
@Service
@Slf4j
public class EmbeddedTransactionService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 多文档事务操作
     */
    @Transactional
    public void updateWithTransaction(String userId, Address newAddress, Education newEducation) {
        try {
            // 更新地址
            Query query1 = Query.query(Criteria.where("userId").is(userId));
            Update update1 = Update.update("address", newAddress);
            mongoTemplate.updateFirst(query1, update1, User.class);
            
            // 添加教育经历
            Query query2 = Query.query(Criteria.where("userId").is(userId));
            Update update2 = new Update().push("educationHistory", newEducation);
            mongoTemplate.updateFirst(query2, update2, User.class);
            
            // 更新相关文档
            updateRelatedDocuments(userId);
            
        } catch (Exception e) {
            log.error("Transaction failed", e);
            throw new RuntimeException("Transaction failed", e);
        }
    }
    
    private void updateRelatedDocuments(String userId) {
        // 更新相关文档的逻辑
    }
}

七、监控与调试

7.1 查询性能监控

java 复制代码
@Aspect
@Component
@Slf4j
public class EmbeddedQueryMonitor {
    
    @Around("execution(* com.example.repository.*.*(..))")
    public Object monitorQueryPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            
            if (duration > 100) { // 超过100ms的记录为慢查询
                log.warn("Slow query detected: {} took {}ms", 
                    joinPoint.getSignature().getName(), duration);
                
                // 记录慢查询详情
                if (log.isDebugEnabled()) {
                    Object[] args = joinPoint.getArgs();
                    log.debug("Query arguments: {}", Arrays.toString(args));
                }
            }
        }
    }
}

7.2 内嵌文档验证

java 复制代码
@Component
public class EmbeddedDocumentValidator {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 验证内嵌文档结构
     */
    public ValidationResult validateUserDocument(String userId) {
        User user = mongoTemplate.findById(userId, User.class);
        
        ValidationResult result = new ValidationResult();
        
        if (user == null) {
            result.addError("User not found");
            return result;
        }
        
        // 验证地址
        if (user.getAddress() != null) {
            validateAddress(user.getAddress(), result);
        }
        
        // 验证教育经历
        if (user.getEducationHistory() != null) {
            validateEducations(user.getEducationHistory(), result);
        }
        
        // 验证工作经历
        if (user.getWorkExperiences() != null) {
            validateWorkExperiences(user.getWorkExperiences(), result);
        }
        
        return result;
    }
    
    private void validateAddress(Address address, ValidationResult result) {
        if (StringUtils.isEmpty(address.getCity())) {
            result.addWarning("Address city is empty");
        }
        
        if (address.getLocation() != null) {
            if (address.getLocation().getLatitude() < -90 || 
                address.getLocation().getLatitude() > 90) {
                result.addError("Invalid latitude");
            }
        }
    }
    
    // 其他验证方法...
    
    @Data
    public static class ValidationResult {
        private List<String> errors = new ArrayList<>();
        private List<String> warnings = new ArrayList<>();
        
        public void addError(String error) {
            errors.add(error);
        }
        
        public void addWarning(String warning) {
            warnings.add(warning);
        }
        
        public boolean isValid() {
            return errors.isEmpty();
        }
    }
}

八、总结

8.1 核心要点回顾

  1. 设计原则

    • 将频繁一起访问的数据内嵌
    • 内嵌文档不应无限制增长
    • 考虑读写比例和更新频率
  2. 性能优化

    • 为查询频繁的内嵌字段创建索引
    • 使用投影限制返回字段
    • 监控文档大小,避免超过16MB限制
  3. 最佳实践

    • 使用合适的模式(子集模式、扩展引用模式等)
    • 实现适当的验证和清理机制
    • 考虑数据迁移和版本控制

8.2 选择内嵌 vs 引用

场景 建议 示例
一对一关系,数据不独立 内嵌 用户地址
一对多关系,子文档数量有限 内嵌 订单项
多对多关系 引用 用户-角色
大型文档,频繁更新 引用 博客文章和评论
需要独立查询子文档 引用 产品和分类
相关推荐
电商API_180079052472 小时前
淘宝商品数据爬虫技术实践指南
大数据·数据库·人工智能·爬虫
酸菜牛肉汤面2 小时前
17、什么是脏读?幻读?不可重复读?
java·数据库·mysql
ClouGence2 小时前
数据实时迁移同步工具 CloudCanal-v5.3.1.0 发布,支持金仓数据库
大数据·数据库·mysql·数据分析·dba
怪我冷i2 小时前
GORM 的 Migration API
数据库·postgresql·golang·ai编程·ai写作
Miss_Chenzr2 小时前
Springboot快递信息管理52c05本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·数据库·spring boot
醉卧考场君莫笑2 小时前
数据分析理论基础
java·数据库·数据分析
czlczl200209252 小时前
MybatisPlusInterceptor实现无感修改SQL的底层原理(源码)
数据库·spring boot·后端·sql
yumgpkpm2 小时前
银行的数据智能平台和Cloudera CDP 7.3(CMP 7.3)的技术对接
数据库·人工智能·hive·hadoop·elasticsearch·数据挖掘·kafka
optimistic_chen2 小时前
【Redis 系列】常用数据结构---String类型
数据结构·数据库·redis·缓存·string