Spring?Boot中MongoTemplate從入門到實(shí)戰(zhàn)深度解析
一、MongoTemplate概述
1.1 什么是MongoTemplate
MongoTemplate是Spring Data MongoDB提供的核心類,它封裝了MongoDB的Java驅(qū)動,提供了豐富的方法來操作MongoDB數(shù)據(jù)庫。相比直接使用MongoDB驅(qū)動,MongoTemplate提供了更簡潔、更符合Spring風(fēng)格的API。
主要特性:
- 豐富的CRUD操作
- 強(qiáng)大的查詢構(gòu)建器
- 聚合框架支持
- 網(wǎng)格文件系統(tǒng)(GridFS)支持
- 聲明式事務(wù)支持
- 自動類型轉(zhuǎn)換
1.2 核心依賴配置
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Starter Data MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- 如果需要Reactive支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<!-- Lombok簡化代碼 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>1.3 配置文件
# application.yml
spring:
data:
mongodb:
# 單機(jī)配置
host: localhost
port: 27017
database: testdb
username: admin
password: secret
# 連接池配置
auto-index-creation: true
authentication-database: admin
# 副本集配置
# uri: mongodb://user:pass@host1:port1,host2:port2/database?replicaSet=rs0
# 連接超時配置
connection-per-host: 100
min-connections-per-host: 10
max-wait-time: 120000
connect-timeout: 10000
socket-timeout: 0
# SSL配置
ssl:
enabled: false
# 監(jiān)控配置
mongodb:
metrics:
enabled: true二、MongoTemplate核心配置
2.1 基礎(chǔ)配置類
@Configuration
@EnableMongoAuditing // 啟用審計(jì)功能
public class MongoConfig {
@Value("${spring.data.mongodb.database}")
private String database;
@Value("${spring.data.mongodb.host}")
private String host;
@Value("${spring.data.mongodb.port}")
private int port;
/**
* 方式一:使用屬性配置創(chuàng)建MongoClient
*/
@Bean
public MongoClient mongoClient() {
ConnectionString connectionString = new ConnectionString(
String.format("mongodb://%s:%d/%s", host, port, database)
);
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.applyToConnectionPoolSettings(builder ->
builder.maxSize(100) // 連接池最大連接數(shù)
.minSize(10) // 最小連接數(shù)
.maxWaitTime(2, TimeUnit.MINUTES) // 最大等待時間
)
.applyToSocketSettings(builder ->
builder.connectTimeout(10, TimeUnit.SECONDS) // 連接超時
.readTimeout(30, TimeUnit.SECONDS) // 讀取超時
)
.applyToSslSettings(builder ->
builder.enabled(false) // 禁用SSL
)
.codecRegistry(CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(PojoCodecProvider.builder()
.automatic(true)
.build())
))
.build();
return MongoClients.create(settings);
}
/**
* 方式二:使用MongoTemplate Bean
*/
@Bean
public MongoTemplate mongoTemplate(MongoClient mongoClient) {
return new MongoTemplate(mongoClient, database);
}
/**
* 配置自定義轉(zhuǎn)換器
*/
@Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new LocalDateTimeToStringConverter());
converters.add(new StringToLocalDateTimeConverter());
converters.add(new BigDecimalToDecimal128Converter());
converters.add(new Decimal128ToBigDecimalConverter());
return new MongoCustomConversions(converters);
}
/**
* 自定義類型映射
*/
@Bean
public MappingMongoConverter mappingMongoConverter(
MongoDatabaseFactory factory,
MongoMappingContext context,
MongoCustomConversions conversions) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, context);
converter.setCustomConversions(conversions);
converter.setTypeMapper(new DefaultMongoTypeMapper(null)); // 不保存_class字段
// 配置字段映射策略
converter.setMapKeyDotReplacement("_"); // 將Map key中的點(diǎn)替換為下劃線
return converter;
}
/**
* 自定義轉(zhuǎn)換器示例
*/
public class LocalDateTimeToStringConverter implements Converter<LocalDateTime, String> {
@Override
public String convert(LocalDateTime source) {
return source.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(source, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
}2.2 多數(shù)據(jù)源配置
@Configuration
public class MultiMongoConfig {
/**
* 主數(shù)據(jù)源配置
*/
@Configuration
@EnableMongoRepositories(
basePackages = "com.example.repository.primary",
mongoTemplateRef = "primaryMongoTemplate"
)
public static class PrimaryMongoConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.data.mongodb.primary")
public MongoProperties primaryProperties() {
return new MongoProperties();
}
@Primary
@Bean(name = "primaryMongoClient")
public MongoClient primaryMongoClient() {
MongoProperties properties = primaryProperties();
return MongoClients.create(
MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(properties.getUri()))
.build()
);
}
@Primary
@Bean(name = "primaryMongoTemplate")
public MongoTemplate primaryMongoTemplate() {
return new MongoTemplate(primaryMongoClient(),
primaryProperties().getDatabase());
}
}
/**
* 次數(shù)據(jù)源配置
*/
@Configuration
@EnableMongoRepositories(
basePackages = "com.example.repository.secondary",
mongoTemplateRef = "secondaryMongoTemplate"
)
public static class SecondaryMongoConfig {
@Bean
@ConfigurationProperties(prefix = "spring.data.mongodb.secondary")
public MongoProperties secondaryProperties() {
return new MongoProperties();
}
@Bean(name = "secondaryMongoClient")
public MongoClient secondaryMongoClient() {
MongoProperties properties = secondaryProperties();
return MongoClients.create(
MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(properties.getUri()))
.build()
);
}
@Bean(name = "secondaryMongoTemplate")
public MongoTemplate secondaryMongoTemplate() {
return new MongoTemplate(secondaryMongoClient(),
secondaryProperties().getDatabase());
}
}
}2.3 實(shí)體類映射配置
/**
* 基礎(chǔ)實(shí)體類
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Document(collection = "base_entities")
@Inheritance // 支持繼承
public abstract class BaseEntity {
@Id
private String id;
@CreatedDate
@Field("created_at")
private LocalDateTime createdAt;
@LastModifiedDate
@Field("updated_at")
private LocalDateTime updatedAt;
@CreatedBy
@Field("created_by")
private String createdBy;
@LastModifiedBy
@Field("updated_by")
private String updatedBy;
@Version
private Long version;
@Field("is_deleted")
private Boolean isDeleted = false;
/**
* 軟刪除標(biāo)記
*/
public void softDelete() {
this.isDeleted = true;
this.updatedAt = LocalDateTime.now();
}
}
/**
* 用戶實(shí)體類
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Document(collection = "users")
@CompoundIndexes({
@CompoundIndex(name = "idx_username_email", def = "{'username': 1, 'email': 1}"),
@CompoundIndex(name = "idx_created_at", def = "{'createdAt': -1}")
})
@TypeAlias("user") // 類型別名
public class User extends BaseEntity {
@Indexed(unique = true, background = true)
@Field("username")
private String username;
@Indexed
@Field("email")
@Email
private String email;
@Field("age")
@Min(0)
@Max(150)
private Integer age;
@Field("address")
private Address address;
@Field("roles")
private Set<String> roles = new HashSet<>();
@Field("metadata")
private Map<String, Object> metadata = new HashMap<>();
@Field("profile")
private UserProfile profile;
@Field("status")
@DBRef(lazy = true) // 懶加載引用
private Status status;
// 自定義轉(zhuǎn)換器
@Field("custom_field")
@PersistenceConstructor
public User(String id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
}
/**
* 地址值對象
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Address {
@Field("street")
private String street;
@Field("city")
private String city;
@Field("postal_code")
private String postalCode;
@Field("country")
private String country;
// 地理空間數(shù)據(jù)
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
@Field("location")
private GeoJsonPoint location;
}
/**
* 審計(jì)配置
*/
@Configuration
public class AuditorConfig implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
// 從安全上下文獲取當(dāng)前用戶
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.of("system");
}
return Optional.of(authentication.getName());
}
}三、基礎(chǔ)CRUD操作
3.1 插入操作
@Service
@Slf4j
@Transactional
public class UserService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 插入單個文檔
*/
public User insertUser(User user) {
// 設(shè)置創(chuàng)建時間
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
// 插入并返回
User savedUser = mongoTemplate.insert(user);
log.info("Inserted user: {}", savedUser.getId());
return savedUser;
}
/**
* 批量插入文檔
*/
public List<User> batchInsertUsers(List<User> users) {
// 設(shè)置時間戳
users.forEach(user -> {
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
});
// 批量插入
Collection<User> insertedUsers = mongoTemplate.insertAll(users);
log.info("Batch inserted {} users", insertedUsers.size());
return new ArrayList<>(insertedUsers);
}
/**
* 保存或更新(upsert)
*/
public User saveOrUpdateUser(User user) {
if (user.getId() == null) {
return insertUser(user);
} else {
return updateUser(user);
}
}
/**
* 使用BulkOperations批量操作
*/
public BulkWriteResult bulkInsertUsers(List<User> users) {
BulkOperations bulkOps = mongoTemplate.bulkOps(
BulkOperations.BulkMode.ORDERED, User.class);
List<InsertOneModel<User>> operations = users.stream()
.map(user -> {
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return new InsertOneModel<>(user);
})
.collect(Collectors.toList());
bulkOps.insert(users);
return bulkOps.execute();
}
}3.2 查詢操作
@Service
@Slf4j
public class UserQueryService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 基礎(chǔ)查詢方法
*/
// 根據(jù)ID查詢
public User findById(String id) {
return mongoTemplate.findById(id, User.class);
}
// 查詢所有
public List<User> findAll() {
return mongoTemplate.findAll(User.class);
}
// 查詢所有(指定集合)
public List<User> findAllInCollection(String collectionName) {
return mongoTemplate.findAll(User.class, collectionName);
}
// 條件查詢
public List<User> findByUsername(String username) {
Query query = Query.query(Criteria.where("username").is(username));
return mongoTemplate.find(query, User.class);
}
// 多條件查詢
public List<User> findByCityAndAgeGreaterThan(String city, int minAge) {
Query query = Query.query(
Criteria.where("address.city").is(city)
.and("age").gte(minAge)
);
return mongoTemplate.find(query, User.class);
}
// 模糊查詢
public List<User> findByNameLike(String namePattern) {
Query query = Query.query(
Criteria.where("username").regex(namePattern, "i") // 忽略大小寫
);
return mongoTemplate.find(query, User.class);
}
// IN查詢
public List<User> findByIds(List<String> ids) {
Query query = Query.query(Criteria.where("id").in(ids));
return mongoTemplate.find(query, User.class);
}
// NOT查詢
public List<User> findByCityNot(String city) {
Query query = Query.query(Criteria.where("address.city").ne(city));
return mongoTemplate.find(query, User.class);
}
// NULL查詢
public List<User> findUsersWithNullEmail() {
Query query = Query.query(Criteria.where("email").is(null));
return mongoTemplate.find(query, User.class);
}
// EXISTS查詢
public List<User> findUsersWithAddress() {
Query query = Query.query(Criteria.where("address").exists(true));
return mongoTemplate.find(query, User.class);
}
/**
* 分頁查詢
*/
public Page<User> findUsersWithPagination(int page, int size, String sortField) {
Query query = new Query();
// 總數(shù)
long total = mongoTemplate.count(query, User.class);
// 分頁
query.skip((long) (page - 1) * size)
.limit(size);
// 排序
if (StringUtils.isNotBlank(sortField)) {
query.with(Sort.by(Sort.Direction.ASC, sortField));
}
// 查詢數(shù)據(jù)
List<User> content = mongoTemplate.find(query, User.class);
return new PageImpl<>(content, PageRequest.of(page - 1, size), total);
}
/**
* 投影查詢(只返回指定字段)
*/
public List<User> findUsersWithProjection() {
Query query = new Query();
// 指定返回的字段
query.fields()
.include("username")
.include("email")
.include("address.city")
.exclude("id")
.exclude("createdAt")
.exclude("updatedAt");
return mongoTemplate.find(query, User.class);
}
/**
* 使用Distinct去重
*/
public List<String> findDistinctCities() {
return mongoTemplate.findDistinct(
new Query(), "address.city", User.class, String.class);
}
/**
* 地理空間查詢
*/
public List<User> findUsersNearLocation(double longitude, double latitude, double maxDistanceInMeters) {
Query query = Query.query(
Criteria.where("address.location")
.near(new Point(longitude, latitude))
.maxDistance(maxDistanceInMeters)
);
return mongoTemplate.find(query, User.class);
}
/**
* 使用游標(biāo)處理大數(shù)據(jù)集
*/
public void processLargeUserDataset() {
Query query = Query.query(Criteria.where("age").gte(18));
try (MongoCursor<User> cursor = mongoTemplate.stream(query, User.class)) {
while (cursor.hasNext()) {
User user = cursor.next();
// 處理每個用戶
processUser(user);
}
}
}
/**
* 查詢一個文檔
*/
public User findOneUser(String username) {
Query query = Query.query(Criteria.where("username").is(username));
return mongoTemplate.findOne(query, User.class);
}
/**
* 判斷文檔是否存在
*/
public boolean exists(String username) {
Query query = Query.query(Criteria.where("username").is(username));
return mongoTemplate.exists(query, User.class);
}
/**
* 獲取查詢數(shù)量
*/
public long countUsersByCity(String city) {
Query query = Query.query(Criteria.where("address.city").is(city));
return mongoTemplate.count(query, User.class);
}
private void processUser(User user) {
// 處理邏輯
}
}3.3 更新操作
@Service
@Slf4j
@Transactional
public class UserUpdateService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 更新整個文檔
*/
public User updateUser(User user) {
user.setUpdatedAt(LocalDateTime.now());
return mongoTemplate.save(user);
}
/**
* 部分更新
*/
public UpdateResult updateUserEmail(String userId, String newEmail) {
Query query = Query.query(Criteria.where("id").is(userId));
Update update = Update.update("email", newEmail)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 更新多個文檔
*/
public UpdateResult updateUsersAgeByCity(String city, int newAge) {
Query query = Query.query(Criteria.where("address.city").is(city));
Update update = Update.update("age", newAge)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateMulti(query, update, User.class);
}
/**
* Upsert操作(不存在則插入)
*/
public UpdateResult upsertUser(User user) {
Query query = Query.query(Criteria.where("username").is(user.getUsername()));
Update update = new Update()
.set("email", user.getEmail())
.set("age", user.getAge())
.set("updatedAt", LocalDateTime.now())
.setOnInsert("createdAt", LocalDateTime.now());
return mongoTemplate.upsert(query, update, User.class);
}
/**
* 原子操作:遞增字段
*/
public UpdateResult incrementUserAge(String userId, int increment) {
Query query = Query.query(Criteria.where("id").is(userId));
Update update = new Update().inc("age", increment)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 原子操作:乘除運(yùn)算
*/
public UpdateResult multiplyUserScore(String userId, double multiplier) {
Query query = Query.query(Criteria.where("id").is(userId));
Update update = new Update().multiply("score", multiplier)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 數(shù)組操作:向數(shù)組添加元素
*/
public UpdateResult addRoleToUser(String userId, String role) {
Query query = Query.query(Criteria.where("id").is(userId));
Update update = new Update().addToSet("roles", role)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 數(shù)組操作:從數(shù)組移除元素
*/
public UpdateResult removeRoleFromUser(String userId, String role) {
Query query = Query.query(Criteria.where("id").is(userId));
Update update = new Update().pull("roles", role)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 數(shù)組操作:向數(shù)組添加多個元素
*/
public UpdateResult addRolesToUser(String userId, List<String> roles) {
Query query = Query.query(Criteria.where("id").is(userId));
Update update = new Update().pushAll("roles", roles.toArray())
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 數(shù)組操作:更新數(shù)組中的特定元素
*/
public UpdateResult updateArrayElement(String userId, String oldRole, String newRole) {
Query query = Query.query(Criteria.where("id").is(userId)
.and("roles").is(oldRole));
Update update = Update.update("roles.$", newRole)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 使用FindAndModify原子操作
*/
public User findAndUpdateUser(String userId, Update update) {
Query query = Query.query(Criteria.where("id").is(userId));
return mongoTemplate.findAndModify(
query,
update,
FindAndModifyOptions.options().returnNew(true),
User.class
);
}
/**
* 批量更新操作
*/
public BulkWriteResult bulkUpdateUsers(List<User> users) {
BulkOperations bulkOps = mongoTemplate.bulkOps(
BulkOperations.BulkMode.ORDERED, User.class);
for (User user : users) {
Query query = Query.query(Criteria.where("id").is(user.getId()));
Update update = Update.update("email", user.getEmail())
.set("updatedAt", LocalDateTime.now());
bulkOps.updateOne(query, update);
}
return bulkOps.execute();
}
}3.4 刪除操作
@Service
@Slf4j
@Transactional
public class UserDeleteService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 根據(jù)ID刪除
*/
public DeleteResult deleteById(String id) {
Query query = Query.query(Criteria.where("id").is(id));
return mongoTemplate.remove(query, User.class);
}
/**
* 根據(jù)條件刪除
*/
public DeleteResult deleteByUsername(String username) {
Query query = Query.query(Criteria.where("username").is(username));
return mongoTemplate.remove(query, User.class);
}
/**
* 刪除所有文檔
*/
public DeleteResult deleteAllUsers() {
return mongoTemplate.remove(new Query(), User.class);
}
/**
* 刪除集合(慎用!)
*/
public void dropCollection() {
mongoTemplate.dropCollection(User.class);
}
/**
* 軟刪除
*/
public UpdateResult softDeleteUser(String userId) {
Query query = Query.query(Criteria.where("id").is(userId));
Update update = Update.update("isDeleted", true)
.set("updatedAt", LocalDateTime.now());
return mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 批量刪除
*/
public DeleteResult deleteUsersByCity(String city) {
Query query = Query.query(Criteria.where("address.city").is(city));
return mongoTemplate.remove(query, User.class);
}
/**
* 使用FindAndRemove
*/
public User findAndRemove(String username) {
Query query = Query.query(Criteria.where("username").is(username));
return mongoTemplate.findAndRemove(query, User.class);
}
/**
* 刪除所有軟刪除的文檔
*/
public DeleteResult deleteAllSoftDeleted() {
Query query = Query.query(Criteria.where("isDeleted").is(true));
return mongoTemplate.remove(query, User.class);
}
}四、高級查詢與聚合操作
4.1 復(fù)雜條件查詢
@Service
@Slf4j
public class AdvancedQueryService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 組合查詢條件
*/
public List<User> findUsersWithComplexCriteria(String city, Integer minAge, Integer maxAge) {
Criteria criteria = new Criteria();
// 城市條件
if (StringUtils.isNotBlank(city)) {
criteria.and("address.city").is(city);
}
// 年齡范圍條件
if (minAge != null && maxAge != null) {
criteria.and("age").gte(minAge).lte(maxAge);
} else if (minAge != null) {
criteria.and("age").gte(minAge);
} else if (maxAge != null) {
criteria.and("age").lte(maxAge);
}
// 郵箱不為空
criteria.and("email").ne(null);
Query query = Query.query(criteria);
return mongoTemplate.find(query, User.class);
}
/**
* OR條件查詢
*/
public List<User> findUsersByCityOrAge(String city, Integer age) {
Criteria criteria = new Criteria().orOperator(
Criteria.where("address.city").is(city),
Criteria.where("age").is(age)
);
Query query = Query.query(criteria);
return mongoTemplate.find(query, User.class);
}
/**
* AND和OR組合查詢
*/
public List<User> findUsersWithAndOr() {
Criteria criteria = new Criteria().andOperator(
Criteria.where("age").gte(18).lte(60),
new Criteria().orOperator(
Criteria.where("address.city").is("Beijing"),
Criteria.where("address.city").is("Shanghai")
)
);
Query query = Query.query(criteria);
return mongoTemplate.find(query, User.class);
}
/**
* 正則表達(dá)式查詢
*/
public List<User> findUsersByEmailPattern(String pattern) {
Query query = Query.query(
Criteria.where("email").regex(pattern, "i") // i表示忽略大小寫
);
return mongoTemplate.find(query, User.class);
}
/**
* 數(shù)組查詢:包含所有元素
*/
public List<User> findUsersWithAllRoles(List<String> requiredRoles) {
Query query = Query.query(
Criteria.where("roles").all(requiredRoles)
);
return mongoTemplate.find(query, User.class);
}
/**
* 數(shù)組查詢:包含任意元素
*/
public List<User> findUsersWithAnyRole(List<String> roles) {
Query query = Query.query(
Criteria.where("roles").in(roles)
);
return mongoTemplate.find(query, User.class);
}
/**
* 數(shù)組查詢:數(shù)組大小
*/
public List<User> findUsersWithRoleCount(int minCount) {
Query query = Query.query(
Criteria.where("roles").size(minCount)
);
return mongoTemplate.find(query, User.class);
}
/**
* 日期范圍查詢
*/
public List<User> findUsersCreatedBetween(LocalDateTime start, LocalDateTime end) {
Query query = Query.query(
Criteria.where("createdAt").gte(start).lte(end)
);
return mongoTemplate.find(query, User.class);
}
/**
* 嵌套文檔查詢
*/
public List<User> findUsersByAddressDetail(String city, String street) {
Query query = Query.query(
Criteria.where("address.city").is(city)
.and("address.street").regex(street, "i")
);
return mongoTemplate.find(query, User.class);
}
/**
* 使用Text Search(需要創(chuàng)建文本索引)
*/
public List<User> findUsersByText(String searchText) {
TextCriteria criteria = TextCriteria.forDefaultLanguage()
.matching(searchText);
Query query = TextQuery.queryText(criteria)
.sortByScore() // 按相關(guān)性排序
.with(PageRequest.of(0, 10));
return mongoTemplate.find(query, User.class);
}
/**
* 地理空間查詢:多邊形范圍內(nèi)
*/
public List<User> findUsersInPolygon(List<double[]> polygonPoints) {
// polygonPoints格式:[[lon1, lat1], [lon2, lat2], ...]
Query query = Query.query(
Criteria.where("address.location")
.within(new GeoJsonPolygon(polygonPoints))
);
return mongoTemplate.find(query, User.class);
}
}4.2 聚合操作
@Service
@Slf4j
public class AggregationService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 基礎(chǔ)聚合:分組統(tǒng)計(jì)
*/
public List<Document> groupUsersByCity() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.group("address.city")
.count().as("userCount")
.avg("age").as("avgAge")
.sum("age").as("totalAge"),
Aggregation.project("userCount", "avgAge", "totalAge")
.and("_id").as("city"),
Aggregation.sort(Sort.Direction.DESC, "userCount"),
Aggregation.limit(10)
);
AggregationResults<Document> results = mongoTemplate.aggregate(
aggregation, "users", Document.class);
return results.getMappedResults();
}
/**
* 多階段聚合:匹配、分組、排序
*/
public List<Document> aggregateActiveUsers() {
Aggregation aggregation = Aggregation.newAggregation(
// 階段1:過濾條件
Aggregation.match(
Criteria.where("isDeleted").is(false)
.and("age").gte(18)
),
// 階段2:按城市分組
Aggregation.group("address.city")
.count().as("activeUserCount")
.avg("age").as("averageAge"),
// 階段3:投影
Aggregation.project()
.and("_id").as("city")
.and("activeUserCount").as("userCount")
.and("averageAge").as("avgAge")
.andExclude("_id"),
// 階段4:排序
Aggregation.sort(Sort.Direction.DESC, "userCount"),
// 階段5:分頁
Aggregation.skip(0L),
Aggregation.limit(20)
);
return mongoTemplate.aggregate(aggregation, "users", Document.class)
.getMappedResults();
}
/**
* 聚合操作:展開數(shù)組
*/
public List<Document> unwindUserRoles() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("roles").exists(true)),
Aggregation.unwind("roles"),
Aggregation.group("roles")
.count().as("userCount"),
Aggregation.sort(Sort.Direction.DESC, "userCount")
);
return mongoTemplate.aggregate(aggregation, "users", Document.class)
.getMappedResults();
}
/**
* 聚合操作:關(guān)聯(lián)查詢
*/
public List<Document> aggregateWithLookup() {
LookupOperation lookup = LookupOperation.newLookup()
.from("orders") // 關(guān)聯(lián)訂單集合
.localField("id")
.foreignField("userId")
.as("userOrders");
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("address.city").is("Beijing")),
lookup,
Aggregation.unwind("userOrders", true), // 保留沒有訂單的用戶
Aggregation.group("id")
.first("username").as("username")
.sum("userOrders.amount").as("totalSpent")
.count().as("orderCount"),
Aggregation.match(Criteria.where("orderCount").gt(0)),
Aggregation.sort(Sort.Direction.DESC, "totalSpent")
);
return mongoTemplate.aggregate(aggregation, "users", Document.class)
.getMappedResults();
}
/**
* 聚合操作:添加計(jì)算字段
*/
public List<Document> addComputedField() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.project()
.and("username").as("username")
.and("age").as("age")
.andExpression("year(createdAt)").as("creationYear")
.andExpression("concat(address.city, ', ', address.country)").as("fullAddress")
.andExpression("CASE WHEN age >= 18 THEN 'Adult' ELSE 'Minor' END").as("ageGroup"),
Aggregation.sort(Sort.Direction.ASC, "creationYear")
);
return mongoTemplate.aggregate(aggregation, "users", Document.class)
.getMappedResults();
}
/**
* 聚合操作:分桶
*/
public List<Document> bucketUsersByAge() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.bucket("age")
.withBoundaries(0, 18, 30, 50, 100)
.withDefaultBucket("Unknown")
.andOutputCount().as("count")
.andOutput("username").push().as("users")
);
return mongoTemplate.aggregate(aggregation, "users", Document.class)
.getMappedResults();
}
/**
* 聚合操作:抽樣
*/
public List<User> sampleUsers(int sampleSize) {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.sample(sampleSize)
);
return mongoTemplate.aggregate(aggregation, "users", User.class)
.getMappedResults();
}
/**
* 聚合操作:圖形查找
*/
public List<Document> graphLookup() {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("username").is("admin")),
Aggregation.graphLookup("users")
.startWith("$reportsTo")
.connectFrom("reportsTo")
.connectTo("id")
.as("managersChain")
);
return mongoTemplate.aggregate(aggregation, "users", Document.class)
.getMappedResults();
}
}4.3 事務(wù)管理
@Service
@Slf4j
@Transactional
public class TransactionalService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 聲明式事務(wù)
*/
@Transactional(rollbackFor = Exception.class)
public void transferPoints(String fromUserId, String toUserId, int points) {
// 減少轉(zhuǎn)出用戶的積分
Query query1 = Query.query(Criteria.where("id").is(fromUserId));
Update update1 = Update.update("points", points).inc("points", -points);
UpdateResult result1 = mongoTemplate.updateFirst(query1, update1, User.class);
if (result1.getModifiedCount() == 0) {
throw new RuntimeException("轉(zhuǎn)出用戶不存在或積分不足");
}
// 增加轉(zhuǎn)入用戶的積分
Query query2 = Query.query(Criteria.where("id").is(toUserId));
Update update2 = Update.update("points", points).inc("points", points);
UpdateResult result2 = mongoTemplate.updateFirst(query2, update2, User.class);
if (result2.getModifiedCount() == 0) {
throw new RuntimeException("轉(zhuǎn)入用戶不存在");
}
// 記錄交易日志
TransactionLog log = TransactionLog.builder()
.fromUserId(fromUserId)
.toUserId(toUserId)
.points(points)
.timestamp(LocalDateTime.now())
.build();
mongoTemplate.insert(log);
}
/**
* 編程式事務(wù)
*/
public void transferPointsWithProgrammaticTransaction(String fromUserId, String toUserId, int points) {
TransactionTemplate transactionTemplate = new TransactionTemplate(
new MongoTransactionManager(mongoTemplate.getMongoDatabaseFactory())
);
transactionTemplate.execute(status -> {
try {
// 執(zhí)行事務(wù)操作
Query query1 = Query.query(Criteria.where("id").is(fromUserId));
Update update1 = Update.update("points", points).inc("points", -points);
mongoTemplate.updateFirst(query1, update1, User.class);
Query query2 = Query.query(Criteria.where("id").is(toUserId));
Update update2 = Update.update("points", points).inc("points", points);
mongoTemplate.updateFirst(query2, update2, User.class);
// 記錄日志
TransactionLog log = TransactionLog.builder()
.fromUserId(fromUserId)
.toUserId(toUserId)
.points(points)
.timestamp(LocalDateTime.now())
.build();
mongoTemplate.insert(log);
return true;
} catch (Exception e) {
status.setRollbackOnly();
throw new RuntimeException("轉(zhuǎn)賬失敗", e);
}
});
}
/**
* 多文檔事務(wù)
*/
@Transactional
public void multiDocumentTransaction() {
// 操作1:更新用戶
User user = new User();
user.setId("user1");
user.setUsername("updated");
mongoTemplate.save(user);
// 操作2:插入日志
AuditLog auditLog = new AuditLog();
auditLog.setAction("UPDATE_USER");
auditLog.setUserId("user1");
mongoTemplate.insert(auditLog);
// 操作3:更新計(jì)數(shù)器
Query query = Query.query(Criteria.where("name").is("user_update_counter"));
Update update = Update.update("count", 1).inc("count", 1);
mongoTemplate.upsert(query, update, Counter.class);
// 如果發(fā)生異常,所有操作都會回滾
}
}五、GridFS操作
@Service
@Slf4j
public class GridFsService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 存儲文件到GridFS
*/
public String storeFile(String filename, InputStream inputStream, String contentType) {
GridFsTemplate gridFsTemplate = new GridFsTemplate(
mongoTemplate.getMongoDatabaseFactory(),
mongoTemplate.getConverter()
);
// 設(shè)置元數(shù)據(jù)
ObjectMetadata metadata = new ObjectMetadata();
metadata.put("uploadedBy", "system");
metadata.put("uploadTime", LocalDateTime.now().toString());
// 存儲文件
GridFSFile file = gridFsTemplate.store(
inputStream,
filename,
contentType,
metadata
);
return file.getObjectId().toString();
}
/**
* 從GridFS讀取文件
*/
public GridFsResource getFile(String fileId) {
GridFsTemplate gridFsTemplate = new GridFsTemplate(
mongoTemplate.getMongoDatabaseFactory(),
mongoTemplate.getConverter()
);
GridFSFile file = gridFsTemplate.findOne(
Query.query(Criteria.where("_id").is(new ObjectId(fileId)))
);
if (file == null) {
throw new RuntimeException("File not found");
}
return gridFsTemplate.getResource(file);
}
/**
* 刪除GridFS文件
*/
public void deleteFile(String fileId) {
GridFsTemplate gridFsTemplate = new GridFsTemplate(
mongoTemplate.getMongoDatabaseFactory(),
mongoTemplate.getConverter()
);
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(new ObjectId(fileId))));
}
/**
* 查詢GridFS文件
*/
public List<GridFSFile> findFilesByMetadata(String key, String value) {
GridFsTemplate gridFsTemplate = new GridFsTemplate(
mongoTemplate.getMongoDatabaseFactory(),
mongoTemplate.getConverter()
);
return gridFsTemplate.find(
Query.query(Criteria.where("metadata." + key).is(value))
);
}
}六、性能優(yōu)化與監(jiān)控
6.1 索引管理
@Service
@Slf4j
public class IndexManagementService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 創(chuàng)建索引
*/
public void createIndexes() {
IndexOperations indexOps = mongoTemplate.indexOps(User.class);
// 創(chuàng)建單字段索引
indexOps.ensureIndex(new Index().on("username", Sort.Direction.ASC));
// 創(chuàng)建唯一索引
indexOps.ensureIndex(new Index().on("email", Sort.Direction.ASC).unique());
// 創(chuàng)建復(fù)合索引
indexOps.ensureIndex(
new Index().on("address.city", Sort.Direction.ASC)
.on("age", Sort.Direction.DESC)
.named("city_age_idx")
);
// 創(chuàng)建TTL索引(自動過期)
indexOps.ensureIndex(
new Index().on("createdAt", Sort.Direction.ASC)
.expire(30, TimeUnit.DAYS) // 30天后自動刪除
);
// 創(chuàng)建文本索引
indexOps.ensureIndex(
new Index().on("username", Sort.Direction.ASC)
.on("email", Sort.Direction.ASC)
.named("text_search_idx")
);
// 創(chuàng)建地理空間索引
indexOps.ensureIndex(
new Index().on("address.location", IndexDirection.GEO_2DSPHERE)
);
// 創(chuàng)建部分索引
indexOps.ensureIndex(
new Index().on("age", Sort.Direction.ASC)
.partial(Filter.filter(Criteria.where("age").gte(18)))
);
}
/**
* 獲取所有索引
*/
public List<Document> getAllIndexes() {
IndexOperations indexOps = mongoTemplate.indexOps(User.class);
return indexOps.getIndexInfo();
}
/**
* 刪除索引
*/
public void dropIndex(String indexName) {
IndexOperations indexOps = mongoTemplate.indexOps(User.class);
indexOps.dropIndex(indexName);
}
/**
* 重建索引
*/
public void rebuildIndexes() {
IndexOperations indexOps = mongoTemplate.indexOps(User.class);
indexOps.dropAllIndexes();
createIndexes();
}
/**
* 檢查索引是否存在
*/
public boolean indexExists(String indexName) {
IndexOperations indexOps = mongoTemplate.indexOps(User.class);
return indexOps.getIndexInfo().stream()
.anyMatch(index -> index.getName().equals(indexName));
}
}6.2 性能監(jiān)控
@Aspect
@Component
@Slf4j
public class MongoPerformanceMonitor {
private final MeterRegistry meterRegistry;
public MongoPerformanceMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Around("execution(* org.springframework.data.mongodb.core.MongoTemplate.*(..))")
public Object monitorMongoOperations(ProceedingJoinPoint joinPoint) throws Throwable {
String operationName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
// 記錄指標(biāo)
meterRegistry.timer("mongodb.operations", "operation", operationName)
.record(duration, TimeUnit.MILLISECONDS);
// 記錄慢查詢
if (duration > 1000) { // 超過1秒
log.warn("Slow MongoDB operation detected: {} took {}ms",
operationName, duration);
if (log.isDebugEnabled()) {
Object[] args = joinPoint.getArgs();
log.debug("Operation arguments: {}", Arrays.toString(args));
}
}
return result;
} catch (Exception e) {
// 記錄錯誤指標(biāo)
meterRegistry.counter("mongodb.errors", "operation", operationName)
.increment();
throw e;
}
}
}
/**
* 健康檢查配置
*/
@Component
public class MongoHealthIndicator implements HealthIndicator {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public Health health() {
try {
// 執(zhí)行簡單查詢檢查連接
Document stats = mongoTemplate.executeCommand("{ serverStatus: 1 }");
return Health.up()
.withDetail("version", stats.get("version"))
.withDetail("host", mongoTemplate.getDb().getName())
.withDetail("collections", mongoTemplate.getCollectionNames())
.build();
} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
}
}6.3 查詢優(yōu)化
@Service
@Slf4j
public class QueryOptimizationService {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 使用投影優(yōu)化查詢
*/
public List<User> optimizedFindUsers() {
Query query = new Query();
// 只返回需要的字段
query.fields()
.include("username")
.include("email")
.exclude("_id");
// 限制返回?cái)?shù)量
query.limit(100);
// 使用索引覆蓋查詢
query.withHint("username_1_email_1");
return mongoTemplate.find(query, User.class);
}
/**
* 批量操作優(yōu)化
*/
public void batchOptimizedOperations() {
// 使用BulkOperations進(jìn)行批量操作
BulkOperations bulkOps = mongoTemplate.bulkOps(
BulkOperations.BulkMode.UNORDERED, User.class);
// 批量插入
List<User> usersToInsert = generateUsers(1000);
bulkOps.insert(usersToInsert);
// 批量更新
Query updateQuery = Query.query(Criteria.where("age").lt(18));
Update update = Update.update("category", "minor");
bulkOps.updateMulti(updateQuery, update);
// 執(zhí)行批量操作
BulkWriteResult result = bulkOps.execute();
log.info("Bulk operation result: {}", result);
}
/**
* 使用游標(biāo)處理大數(shù)據(jù)集
*/
public void processLargeDatasetWithCursor() {
Query query = Query.query(Criteria.where("createdAt")
.gte(LocalDateTime.now().minusMonths(1)));
// 設(shè)置游標(biāo)選項(xiàng)
query.cursorBatchSize(1000); // 每批獲取1000條
try (MongoCursor<User> cursor = mongoTemplate.stream(query, User.class)) {
int count = 0;
while (cursor.hasNext()) {
User user = cursor.next();
processUser(user);
count++;
// 每處理1000條記錄一次日志
if (count % 1000 == 0) {
log.info("Processed {} records", count);
}
}
}
}
/**
* 使用聚合管道優(yōu)化復(fù)雜查詢
*/
public List<Document> optimizedAggregation() {
Aggregation aggregation = Aggregation.newAggregation(
// 盡早過濾數(shù)據(jù)
Aggregation.match(Criteria.where("isActive").is(true)),
// 使用索引字段進(jìn)行排序
Aggregation.sort(Sort.Direction.DESC, "createdAt"),
// 限制數(shù)據(jù)量
Aggregation.limit(1000),
// 只投影需要的字段
Aggregation.project()
.and("username").as("name")
.and("email").as("contact")
.andExclude("_id"),
// 最后進(jìn)行復(fù)雜計(jì)算
Aggregation.addFields()
.addField("nameLength").withValueOf(StringOperators.strLenCP("$name"))
.build()
);
return mongoTemplate.aggregate(aggregation, "users", Document.class)
.getMappedResults();
}
private List<User> generateUsers(int count) {
List<User> users = new ArrayList<>();
for (int i = 0; i < count; i++) {
User user = new User();
user.setUsername("user" + i);
user.setEmail("user" + i + "@example.com");
user.setAge(new Random().nextInt(50) + 18);
users.add(user);
}
return users;
}
private void processUser(User user) {
// 處理邏輯
}
}七、最佳實(shí)踐總結(jié)
7.1 設(shè)計(jì)原則
- 合理使用索引:為查詢頻繁的字段創(chuàng)建索引,注意索引順序
- 避免大文檔:單個文檔不應(yīng)超過16MB,考慮拆分大文檔
- 選擇合適的數(shù)據(jù)模型:根據(jù)查詢模式選擇內(nèi)嵌或引用
- 使用合適的字段類型:選擇最合適的BSON類型存儲數(shù)據(jù)
7.2 性能優(yōu)化
- 查詢優(yōu)化:
- 使用投影減少返回字段
- 合理使用分頁和限制
- 避免全表掃描
- 寫入優(yōu)化:
- 使用批量操作
- 合理使用索引(避免過多索引影響寫入性能)
- 考慮寫入確認(rèn)級別
- 連接池優(yōu)化:
- 根據(jù)并發(fā)量調(diào)整連接池大小
- 監(jiān)控連接使用情況
- 合理設(shè)置超時時間
7.3 監(jiān)控告警
- 健康檢查:定期檢查MongoDB連接狀態(tài)
- 性能監(jiān)控:監(jiān)控查詢響應(yīng)時間、錯誤率
- 資源監(jiān)控:監(jiān)控CPU、內(nèi)存、磁盤使用情況
- 慢查詢?nèi)罩?/strong>:記錄和分析慢查詢
7.4 安全實(shí)踐
- 認(rèn)證授權(quán):使用用戶名密碼認(rèn)證
- 網(wǎng)絡(luò)隔離:在內(nèi)網(wǎng)環(huán)境中運(yùn)行
- 加密傳輸:啟用TLS/SSL加密
- 定期備份:制定備份和恢復(fù)策略
八、完整示例項(xiàng)目
8.1 項(xiàng)目結(jié)構(gòu)
src/main/java/com/example/mongodemo/ ├── config/ │ ├── MongoConfig.java │ ├── MongoAuditConfig.java │ └── MultiMongoConfig.java ├── entity/ │ ├── BaseEntity.java │ ├── User.java │ ├── Address.java │ └── AuditLog.java ├── repository/ │ ├── UserRepository.java │ └── CustomUserRepository.java ├── service/ │ ├── UserService.java │ ├── UserQueryService.java │ ├── AggregationService.java │ └── TransactionService.java ├── controller/ │ └── UserController.java └── MongodemoApplication.java
8.2 啟動類配置
@SpringBootApplication
@EnableMongoRepositories
@EnableTransactionManagement
@EnableCaching
@Slf4j
public class MongodemoApplication {
public static void main(String[] args) {
SpringApplication.run(MongodemoApplication.class, args);
}
@Bean
public CommandLineRunner initData(UserService userService) {
return args -> {
log.info("Initializing sample data...");
// 創(chuàng)建測試用戶
User user = User.builder()
.username("testuser")
.email("test@example.com")
.age(25)
.address(Address.builder()
.city("Beijing")
.country("China")
.build())
.build();
userService.insertUser(user);
log.info("Sample user created: {}", user.getUsername());
};
}
}
九、總結(jié)
通過本文的全面介紹,您應(yīng)該已經(jīng)掌握了Spring Boot中MongoTemplate的核心使用方法。關(guān)鍵點(diǎn)包括:
- 配置靈活:支持單機(jī)、副本集、分片集群等多種部署方式
- 操作全面:涵蓋CRUD、聚合、事務(wù)、GridFS等所有操作
- 性能優(yōu)秀:提供豐富的性能優(yōu)化選項(xiàng)和監(jiān)控手段
- 易于集成:與Spring生態(tài)系統(tǒng)無縫集成
在實(shí)際項(xiàng)目中,建議根據(jù)具體業(yè)務(wù)需求選擇合適的操作方式,并持續(xù)監(jiān)控和優(yōu)化數(shù)據(jù)庫性能。
到此這篇關(guān)于Spring Boot中MongoTemplate深度指南:從入門到實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)springboot mongotemplate使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java用BigDecimal類解決Double類型精度丟失的問題
這篇文章主要介紹了Java用BigDecimal類解決Double類型精度丟失的問題,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12
springboot項(xiàng)目部署在linux上運(yùn)行的兩種方式小結(jié)
這篇文章主要介紹了springboot項(xiàng)目部署在linux上運(yùn)行的兩種方式小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
關(guān)于Java中String創(chuàng)建的字符串對象內(nèi)存分配測試問題
這篇文章主要介紹了Java中String創(chuàng)建的字符串對象內(nèi)存分配測試,給大家詳細(xì)介紹了在創(chuàng)建String對象的兩種常用方法比較,通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-07-07
自定義Jackson的ObjectMapper如何實(shí)現(xiàn)@ResponseBody的自定義渲染
這篇文章主要介紹了自定義Jackson的ObjectMapper如何實(shí)現(xiàn)@ResponseBody的自定義渲染,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
Springboot整合spring-boot-starter-data-elasticsearch的過程
本文詳細(xì)介紹了Springboot整合spring-boot-starter-data-elasticsearch的過程,包括版本要求、依賴添加、實(shí)體類添加、索引的名稱、分片、副本設(shè)置等,同時,還介紹了如何使用ElasticsearchRepository類進(jìn)行增刪改查操作2024-10-10
淺談Map集合中g(shù)et不存在的key值,會拋出異常嗎?
這篇文章主要介紹了淺談Map集合中g(shù)et不存在的key值,會拋出異常嗎?具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
springboot 同時啟用http/https的配置方法
本文給大家分享springboot 同時啟用http/https的配置方法,通過修改配置文件、增加java配置的方法來實(shí)現(xiàn)此操作,具體內(nèi)容詳情跟隨小編通過本文學(xué)習(xí)下吧2021-05-05
Spring?Security?中多個身份驗(yàn)證的示例代碼
這篇文章主要介紹了Spring?Security?中多個身份驗(yàn)證的示例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09
SpringBoot啟動報(bào)錯屬性循環(huán)依賴報(bào)錯問題的解決
這篇文章主要介紹了SpringBoot啟動報(bào)錯屬性循環(huán)依賴報(bào)錯問題的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05

