Appearance
Spring Data MongoDB
什么是MongoDB?
MongoDB是一种面向文档的NoSQL数据库,它以JSON风格的BSON (Binary JSON) 格式存储数据,支持动态模式,便于数据表示和存储。MongoDB的主要特性包括高性能、高可用性、自动分片和丰富的查询语言。
Spring Data MongoDB介绍
Spring Data MongoDB是Spring Data家族的一个子项目,提供了对MongoDB数据库的集成支持。它简化了与MongoDB的交互,提供了一致的Spring编程模型,使得开发者能够快速构建基于MongoDB的应用。
主要特性
- 简化的Repository抽象:自动实现基本CRUD操作
- 基于注解的映射:使用@Document等注解将Java对象映射到MongoDB文档
- 模板操作:通过MongoTemplate提供丰富的数据库操作API
- 自动索引创建:基于实体类定义自动创建索引
- 聚合框架支持:支持MongoDB的聚合操作
- 地理空间查询:支持地理位置索引和查询
- 响应式编程支持:提供ReactiveMongoTemplate和ReactiveMongoRepository
- 查询方法生成:根据方法名自动生成查询
依赖配置
Maven
xml
<!-- Spring Boot方式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- 非Spring Boot方式 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>4.1.0</version>
</dependency>
配置MongoDB连接
Spring Boot配置
properties
# 单机连接
spring.data.mongodb.uri=mongodb://localhost:27017/testdb
# 或详细配置
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=testdb
spring.data.mongodb.username=user
spring.data.mongodb.password=secret
spring.data.mongodb.authentication-database=admin
# MongoDB集群配置
spring.data.mongodb.uri=mongodb://user:secret@mongo1.example.com:27017,mongo2.example.com:27017/testdb?replicaSet=rs0
Java配置
java
@Configuration
@EnableMongoRepositories(basePackages = "com.example.repository")
public class MongoConfig extends AbstractMongoClientConfiguration {
@Override
protected String getDatabaseName() {
return "testdb";
}
@Override
public MongoClient mongoClient() {
ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/testdb");
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
// 自定义转换器(可选)
@Override
public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory databaseFactory,
MongoCustomConversions customConversions,
MongoMappingContext mappingContext) {
MappingMongoConverter converter = super.mappingMongoConverter(databaseFactory, customConversions, mappingContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null)); // 禁用_class字段
return converter;
}
}
实体映射
MongoDB文档映射到Java对象:
java
@Document(collection = "users")
public class User {
@Id
private String id;
private String username;
private String email;
@Field("account_status")
private String status;
@CreatedDate
private Date createdDate;
@LastModifiedDate
private Date lastModifiedDate;
@DBRef
private List<Role> roles;
@Transient
private Integer age; // 不会被持久化到MongoDB
// 构造函数、getter和setter
}
@Document(collection = "roles")
public class Role {
@Id
private String id;
private String name;
// 构造函数、getter和setter
}
启用审计功能
java
@Configuration
@EnableMongoAuditing
public class MongoAuditingConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of("SYSTEM");
}
}
MongoRepository
使用MongoRepository接口自动实现CRUD操作:
java
public interface UserRepository extends MongoRepository<User, String> {
// 根据方法名生成查询
User findByUsername(String username);
List<User> findByEmailEndingWith(String emailDomain);
List<User> findByCreatedDateBetween(Date start, Date end);
// 使用@Query注解自定义查询
@Query("{ 'status' : ?0 }")
List<User> findByStatus(String status);
@Query("{ 'username' : { $regex: ?0, $options: 'i' } }")
List<User> findByRegexpUsername(String regex);
// 更新操作
@Query("{ 'username' : ?0 }")
@Update("{ '$set' : { 'status' : ?1 } }")
void updateUserStatus(String username, String status);
// 删除操作
long deleteByStatus(String status);
// 分页和排序
Page<User> findByStatus(String status, Pageable pageable);
// 计数
long countByStatus(String status);
// 检查存在
boolean existsByEmail(String email);
}
使用MongoTemplate
对于更复杂的查询和操作,可以使用MongoTemplate:
java
@Service
public class UserService {
private final MongoTemplate mongoTemplate;
public UserService(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
// 基本CRUD操作
public User save(User user) {
return mongoTemplate.save(user);
}
public User findById(String id) {
return mongoTemplate.findById(id, User.class);
}
public List<User> findAll() {
return mongoTemplate.findAll(User.class);
}
public void delete(User user) {
mongoTemplate.remove(user);
}
// 使用Query和Criteria进行查询
public List<User> findActiveUsersByEmailDomain(String domain) {
Query query = new Query();
query.addCriteria(Criteria.where("email").regex(domain + "$"))
.addCriteria(Criteria.where("status").is("ACTIVE"));
return mongoTemplate.find(query, User.class);
}
// 分页查询
public Page<User> findPaginated(int page, int size) {
Query query = new Query().with(PageRequest.of(page, size));
long total = mongoTemplate.count(query, User.class);
List<User> users = mongoTemplate.find(query, User.class);
return new PageImpl<>(users, PageRequest.of(page, size), total);
}
// 更新操作
public void updateStatus(String id, String newStatus) {
Query query = new Query(Criteria.where("id").is(id));
Update update = new Update().set("status", newStatus);
mongoTemplate.updateFirst(query, update, User.class);
}
// 批量更新
public void deactivateOldUsers(Date cutoffDate) {
Query query = new Query(Criteria.where("lastModifiedDate").lt(cutoffDate));
Update update = new Update().set("status", "INACTIVE");
mongoTemplate.updateMulti(query, update, User.class);
}
// 聚合操作示例
public List<UserStatusCount> countUsersByStatus() {
TypedAggregation<User> aggregation = Aggregation.newAggregation(
User.class,
Aggregation.group("status").count().as("count"),
Aggregation.project("count").and("status").previousOperation()
);
AggregationResults<UserStatusCount> results =
mongoTemplate.aggregate(aggregation, UserStatusCount.class);
return results.getMappedResults();
}
// 嵌套文档查询
public List<User> findByAddressCity(String city) {
Query query = new Query(Criteria.where("address.city").is(city));
return mongoTemplate.find(query, User.class);
}
}
// 聚合结果类
public class UserStatusCount {
private String status;
private long count;
// getter和setter
}
复杂查询示例
条件查询
java
public List<User> findByComplexCriteria(String username, List<String> statuses, Date startDate) {
Query query = new Query();
// 动态添加条件
if (username != null) {
query.addCriteria(Criteria.where("username").regex(username, "i"));
}
if (statuses != null && !statuses.isEmpty()) {
query.addCriteria(Criteria.where("status").in(statuses));
}
if (startDate != null) {
query.addCriteria(Criteria.where("createdDate").gte(startDate));
}
return mongoTemplate.find(query, User.class);
}
嵌套查询
java
@Document
public class Order {
@Id
private String id;
private String orderNumber;
private List<LineItem> items;
// 其他字段和方法
}
public class LineItem {
private String productId;
private int quantity;
private BigDecimal price;
// getter和setter
}
// 查询包含特定产品的订单
public List<Order> findOrdersContainingProduct(String productId) {
Query query = new Query(Criteria.where("items.productId").is(productId));
return mongoTemplate.find(query, Order.class);
}
地理空间查询
java
@Document(collection = "restaurants")
public class Restaurant {
@Id
private String id;
private String name;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private GeoJsonPoint location;
// 其他字段和方法
}
// 查询附近的餐厅
public List<Restaurant> findNearbyRestaurants(double latitude, double longitude, double maxDistance) {
Point point = new Point(longitude, latitude);
Query query = new Query(
Criteria.where("location").nearSphere(point)
.maxDistance(maxDistance / 6378.137) // 转换为弧度(km)
);
return mongoTemplate.find(query, Restaurant.class);
}
事务支持
MongoDB 4.0+版本支持多文档事务:
java
@Service
public class UserRoleService {
private final MongoTemplate mongoTemplate;
private final MongoTransactionManager transactionManager;
public UserRoleService(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) {
this.mongoTemplate = mongoTemplate;
this.transactionManager = transactionManager;
}
@Transactional
public void createUserWithRoles(User user, List<Role> roles) {
// 所有操作都在一个事务中执行
for (Role role : roles) {
mongoTemplate.save(role);
}
user.setRoles(roles);
mongoTemplate.save(user);
}
// 编程式事务
public void transferCredits(String fromUserId, String toUserId, int amount) {
TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
txTemplate.execute(status -> {
User fromUser = mongoTemplate.findById(fromUserId, User.class);
User toUser = mongoTemplate.findById(toUserId, User.class);
if (fromUser.getCredits() < amount) {
throw new InsufficientCreditsException();
}
mongoTemplate.updateFirst(
Query.query(Criteria.where("id").is(fromUserId)),
new Update().inc("credits", -amount),
User.class
);
mongoTemplate.updateFirst(
Query.query(Criteria.where("id").is(toUserId)),
new Update().inc("credits", amount),
User.class
);
return null;
});
}
}
特殊操作
使用GridFS存储大型文件
java
@Service
public class FileService {
private final GridFsTemplate gridFsTemplate;
private final GridFsOperations operations;
public FileService(GridFsTemplate gridFsTemplate, GridFsOperations operations) {
this.gridFsTemplate = gridFsTemplate;
this.operations = operations;
}
// 存储文件
public String storeFile(String filename, InputStream content, String contentType) throws IOException {
ObjectId id = gridFsTemplate.store(
content, filename, contentType, new Document("metadata", "user file")
);
return id.toString();
}
// 检索文件
public GridFSFile getFile(String id) {
return gridFsTemplate.findOne(new Query(Criteria.where("_id").is(new ObjectId(id))));
}
// 获取文件内容
public InputStream getFileContent(GridFSFile file) {
return operations.getResource(file).getInputStream();
}
// 删除文件
public void deleteFile(String id) {
gridFsTemplate.delete(new Query(Criteria.where("_id").is(new ObjectId(id))));
}
}
字段更新操作
java
public void updateUserFields(String userId, Map<String, Object> updates) {
Update update = new Update();
for (Map.Entry<String, Object> entry : updates.entrySet()) {
update.set(entry.getKey(), entry.getValue());
}
mongoTemplate.updateFirst(
Query.query(Criteria.where("id").is(userId)),
update,
User.class
);
}
// 数组操作
public void addRoleToUser(String userId, Role role) {
mongoTemplate.updateFirst(
Query.query(Criteria.where("id").is(userId)),
new Update().push("roles", role),
User.class
);
}
public void removeRoleFromUser(String userId, String roleId) {
mongoTemplate.updateFirst(
Query.query(Criteria.where("id").is(userId)),
new Update().pull("roles", Query.query(Criteria.where("id").is(roleId))),
User.class
);
}
响应式MongoDB
Spring Data MongoDB还提供了响应式编程支持:
java
// 添加依赖
// spring-boot-starter-data-mongodb-reactive
@Configuration
@EnableReactiveMongoRepositories
public class ReactiveMongoConfig extends AbstractReactiveMongoConfiguration {
@Override
protected String getDatabaseName() {
return "testdb";
}
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create("mongodb://localhost:27017");
}
}
// 响应式Repository
public interface ReactiveUserRepository extends ReactiveMongoRepository<User, String> {
Flux<User> findByStatus(String status);
Mono<User> findByUsername(String username);
}
// 使用ReactiveMongoTemplate
@Service
public class ReactiveUserService {
private final ReactiveMongoTemplate template;
public ReactiveUserService(ReactiveMongoTemplate template) {
this.template = template;
}
public Mono<User> save(User user) {
return template.save(user);
}
public Mono<User> findById(String id) {
return template.findById(id, User.class);
}
public Flux<User> findAll() {
return template.findAll(User.class);
}
public Flux<User> findByStatusWithTemplate(String status) {
return template.find(
Query.query(Criteria.where("status").is(status)),
User.class
);
}
}
性能优化
索引管理
java
@Document(collection = "users")
@CompoundIndex(name = "email_username", def = "{'email': 1, 'username': 1}")
public class User {
@Id
private String id;
@Indexed(unique = true)
private String username;
@Indexed
private String email;
@TextIndexed
private String bio;
@Indexed(direction = IndexDirection.DESCENDING)
private Date createdDate;
// 其他字段和方法
}
// 手动创建索引
@PostConstruct
public void initIndices() {
mongoTemplate.indexOps(User.class).ensureIndex(
new Index().on("lastModifiedDate", Direction.DESC)
);
// 复合索引
mongoTemplate.indexOps(User.class).ensureIndex(
new CompoundIndexDefinition(new Document("address.city", 1).append("address.state", 1))
);
// TTL索引
IndexDefinition indexDefinition = new Index("expiresAt", Direction.ASC).expire(60);
mongoTemplate.indexOps(Session.class).ensureIndex(indexDefinition);
}
投影查询
java
// 只返回需要的字段
public List<User> findUserNamesAndEmails() {
Query query = new Query();
query.fields().include("username").include("email");
return mongoTemplate.find(query, User.class);
}
批量操作
java
public void bulkUpdateUserStatus(List<String> userIds, String newStatus) {
BulkOperations bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, User.class);
for (String id : userIds) {
Query query = Query.query(Criteria.where("id").is(id));
Update update = new Update().set("status", newStatus);
bulkOps.updateOne(query, update);
}
BulkWriteResult result = bulkOps.execute();
System.out.println("Modified count: " + result.getModifiedCount());
}
典型应用场景
1. 内容管理系统
MongoDB适合存储结构灵活的内容数据:
java
@Document(collection = "articles")
public class Article {
@Id
private String id;
private String title;
private String content;
private List<String> tags;
private List<Comment> comments;
private Map<String, Object> metadata; // 灵活的元数据
// 其他字段和方法
}
2. 日志和事件存储
java
@Document(collection = "logs")
public class LogEntry {
@Id
private String id;
@Indexed
private Date timestamp;
private String level;
private String message;
private Map<String, Object> data;
// TTL索引可用于自动删除旧日志
}
3. 实时分析
java
// 使用MongoDB聚合管道进行实时统计
public List<DailyUserStats> getDailyActiveUsers(Date startDate, Date endDate) {
TypedAggregation<LogEntry> aggregation = Aggregation.newAggregation(
LogEntry.class,
Aggregation.match(Criteria.where("timestamp").gte(startDate).lte(endDate)),
Aggregation.project()
.andExpression("dateToString('%Y-%m-%d', timestamp)").as("day")
.andExpression("userId").as("userId"),
Aggregation.group("day").addToSet("userId").as("userIds"),
Aggregation.project("userIds")
.and("day").previousOperation()
.and("activeUsers").size("userIds"),
Aggregation.sort(Sort.Direction.ASC, "day")
);
return mongoTemplate.aggregate(aggregation, DailyUserStats.class)
.getMappedResults();
}
故障排除与最佳实践
常见问题
_class字段:默认情况下,Spring Data MongoDB会添加_class字段存储类信息,可以通过自定义MongoConverter禁用
嵌套文档更新:嵌套文档的更新需要使用点符号,如
update.set("address.city", "New York")
DBRef vs 嵌入文档:根据业务需求选择关联方式
ObjectId转换:处理MongoDB的ObjectId时,需要进行适当的类型转换
最佳实践
适当索引:创建合适的索引提高查询性能
文档大小控制:避免文档过大,MongoDB有16MB文档大小限制
使用投影:仅查询需要的字段减少数据传输
分批处理大结果集:使用分页或流式处理大数据集
规划分片键:对于需要分片的集合,选择合适的分片键
使用复合索引:为常见查询创建复合索引
总结
Spring Data MongoDB提供了一个全面的MongoDB集成解决方案,简化了应用程序与MongoDB的交互。通过其Repository抽象、模板操作和注解支持,开发者可以快速构建基于MongoDB的高性能、可扩展的应用程序。
从基本的CRUD操作到高级的聚合查询,从同步API到响应式编程,Spring Data MongoDB提供了全方位的支持,使得MongoDB在Spring生态系统中的使用变得简单而高效。