一、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 核心要点回顾
-
设计原则:
- 将频繁一起访问的数据内嵌
- 内嵌文档不应无限制增长
- 考虑读写比例和更新频率
-
性能优化:
- 为查询频繁的内嵌字段创建索引
- 使用投影限制返回字段
- 监控文档大小,避免超过16MB限制
-
最佳实践:
- 使用合适的模式(子集模式、扩展引用模式等)
- 实现适当的验证和清理机制
- 考虑数据迁移和版本控制
8.2 选择内嵌 vs 引用
| 场景 | 建议 | 示例 |
|---|---|---|
| 一对一关系,数据不独立 | 内嵌 | 用户地址 |
| 一对多关系,子文档数量有限 | 内嵌 | 订单项 |
| 多对多关系 | 引用 | 用户-角色 |
| 大型文档,频繁更新 | 引用 | 博客文章和评论 |
| 需要独立查询子文档 | 引用 | 产品和分类 |