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 引用

场景 建议 示例
一对一关系,数据不独立 内嵌 用户地址
一对多关系,子文档数量有限 内嵌 订单项
多对多关系 引用 用户-角色
大型文档,频繁更新 引用 博客文章和评论
需要独立查询子文档 引用 产品和分类
相关推荐
Trouvaille ~18 分钟前
【MySQL篇】内置函数:数据处理的利器
数据库·mysql·面试·数据清洗·数据处理·dql·基础入门
迦南的迦 亚索的索23 分钟前
PYTHON_DAY20_数据库
数据库·oracle
数厘33 分钟前
2.14 sql数据删除(DELETE、TRUNCATE)
数据库·oracle
XDHCOM1 小时前
MySQL ER_ERROR_ENABLING_KEYS报错修复,远程处理索引启用失败故障,解决数据表锁定与性能瓶颈问题
数据库·mysql
高梦轩1 小时前
Python 操作 MySQL 数据库
数据库·oracle
Arva .1 小时前
Redis 数据类型
数据库·redis·缓存
CDN3601 小时前
高防切换后网站打不开?DNS 解析与回源路径故障排查
前端·网络·数据库
笑我归无处1 小时前
Redis和数据库的数据一致性问题研究
数据库·redis·缓存
水痕012 小时前
使用sqlSugar来操作mysql数据库
数据库·mysql
zandy10112 小时前
衡石科技 HENGSHI SENSE:一站式智能分析平台,让企业数据价值“所见即所得”
大数据·数据库·科技