spring boot 集成mongodb
一、相关依赖
1 2 3 4 5 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <version> 2.0 . 1 .RELEASE</version> </dependency> |
二、配置文件
1 2 | spring.data.mongodb.uri=mongodb: //adminUser:adminPass@localhost:27017/?authSource=admin&authMechanism=SCRAM-SHA-1 spring.data.mongodb.database=users |
1 2 | 多个 IP 集群可以采用以下配置: spring.data.mongodb.uri=mongodb: //user:pwd@ip1:port1,ip2:port2/databases |
三、基本操作方法封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | package com.wcf.mongo.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.mongodb.client.ListIndexesIterable; import com.mongodb.client.model.IndexOptions; import com.mongodb.client.model.Indexes; import com.wcf.mongo.entity.MongoBaseInfo; import org.bson.Document; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @author wangcanfeng * @description 简单的mongodb使用接口 * @Date Created in 17:24-2019/3/20 */ @Service public class SimpleMongoServiceImpl<T extends MongoBaseInfo> implements SimpleMongoService<T> { /** * 注入template,减少重复代码 */ @Autowired private MongoTemplate mongoTemplate; /** * 功能描述: 创建一个集合 * 同一个集合中可以存入多个不同类型的对象,我们为了方便维护和提升性能, * 后续将限制一个集合中存入的对象类型,即一个集合只能存放一个类型的数据 * * @param name 集合名称,相当于传统数据库的表名 * @return:void * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 17:27 */ @Override public void createCollection(String name) { mongoTemplate.createCollection(name); } /** * 功能描述: 创建索引 * 索引是顺序排列,且唯一的索引 * * @param collectionName 集合名称,相当于关系型数据库中的表名 * @param filedName 对象中的某个属性名 * @return:java.lang.String * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 16:13 */ @Override public String createIndex(String collectionName, String filedName) { //配置索引选项 IndexOptions options = new IndexOptions(); // 设置为唯一 options.unique( true ); //创建按filedName升序排的索引 return mongoTemplate.getCollection(collectionName).createIndex(Indexes.ascending(filedName), options); } /** * 功能描述: 获取当前集合对应的所有索引的名称 * * @param collectionName * @return:java.util.List<java.lang.String> * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 16:46 */ @Override public List<String> getAllIndexes(String collectionName) { ListIndexesIterable<Document> list = mongoTemplate.getCollection(collectionName).listIndexes(); //上面的list不能直接获取size,因此初始化arrayList就不设置初始化大小了 List<String> indexes = new ArrayList<>(); for (Document document : list) { document.entrySet().forEach((key) -> { //提取出索引的名称 if (key.getKey().equals( "name" )) { indexes.add(key.getValue().toString()); } }); } return indexes; } /** * 功能描述: 往对应的集合中插入一条数据 * * @param info 存储对象 * @param collectionName 集合名称 * @return:void * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 16:46 */ @Override public void insert(T info, String collectionName) { mongoTemplate.insert(info, collectionName); } /** * 功能描述: 往对应的集合中批量插入数据,注意批量的数据中不要包含重复的id * * @param infos 对象列表 * @return:void * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 16:47 */ @Override public void insertMulti(List<T> infos, String collectionName) { mongoTemplate.insert(infos, collectionName); } /** * 功能描述: 使用索引信息精确更改某条数据 * * @param id 唯一键 * @param collectionName 集合名称 * @param info 待更新的内容 * @return:void * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 18:42 */ @Override public void updateById(String id, String collectionName, T info) { Query query = new Query(Criteria.where( "id" ).is(id)); Update update = new Update(); String str = JSON.toJSONString(info); JSONObject jQuery = JSON.parseObject(str); jQuery.forEach((key, value) -> { //因为id相当于传统数据库中的主键,这里使用时就不支持更新,所以需要剔除掉 if (!key.equals( "id" )) { update.set(key, value); } }); mongoTemplate.updateMulti(query, update, info.getClass(), collectionName); } /** * 功能描述: 根据id删除集合中的内容 * * @param id 序列id * @param collectionName 集合名称 * @param clazz 集合中对象的类型 * @return:void * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 16:47 */ @Override public void deleteById(String id, Class<T> clazz, String collectionName) { // 设置查询条件,当id=#{id} Query query = new Query(Criteria.where( "id" ).is(id)); // mongodb在删除对象的时候会判断对象类型,如果你不传入对象类型,只传入了集合名称,它是找不到的 // 上面我们为了方便管理和提升后续处理的性能,将一个集合限制了一个对象类型,所以需要自行管理一下对象类型 // 在接口传入时需要同时传入对象类型 mongoTemplate.remove(query, clazz, collectionName); } /** * 功能描述: 根据id查询信息 * * @param id 注解 * @param clazz 类型 * @param collectionName 集合名称 * @return:java.util.List<T> * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/20 16:47 */ @Override public T selectById(String id, Class<T> clazz, String collectionName) { // 查询对象的时候,不仅需要传入id这个唯一键,还需要传入对象的类型,以及集合的名称 return mongoTemplate.findById(id, clazz, collectionName); } /** * 功能描述: 查询列表信息 * 将集合中符合对象类型的数据全部查询出来 * * @param collectName 集合名称 * @param clazz 类型 * @return:java.util.List<T> * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/21 10:38 */ @Override public List<T> selectList(String collectName, Class<T> clazz) { return selectList(collectName, clazz, null , null ); } /** * 功能描述: 分页查询列表信息 * * @param collectName 集合名称 * @param clazz 对象类型 * @param currentPage 当前页码 * @param pageSize 分页大小 * @return:java.util.List<T> * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/21 10:38 */ @Override public List<T> selectList(String collectName, Class<T> clazz, Integer currentPage, Integer pageSize) { //设置分页参数 Query query = new Query(); //设置分页信息 if (!ObjectUtils.isEmpty(currentPage) && ObjectUtils.isEmpty(pageSize)) { query.limit(pageSize); query.skip(pageSize * (currentPage - 1 )); } return mongoTemplate.find(query, clazz, collectName); } /** * 功能描述: 根据条件查询集合 * * @param collectName 集合名称 * @param conditions 查询条件,目前查询条件处理的比较简单,仅仅做了相等匹配,没有做模糊查询等复杂匹配 * @param clazz 对象类型 * @param currentPage 当前页码 * @param pageSize 分页大小 * @return:java.util.List<T> * @since: v1.0 * @Author:wangcanfeng * @Date: 2019/3/21 10:48 */ @Override public List<T> selectByCondition(String collectName, Map<String, String> conditions, Class<T> clazz, Integer currentPage, Integer pageSize) { if (ObjectUtils.isEmpty(conditions)) { return selectList(collectName, clazz, currentPage, pageSize); } else { //设置分页参数 Query query = new Query(); query.limit(pageSize); query.skip(currentPage); // 往query中注入查询条件 conditions.forEach((key, value) -> query.addCriteria(Criteria.where(key).is(value))); return mongoTemplate.find(query, clazz, collectName); } } } |
四、连接池配置
mongodb: address: localhost:27017 database: soms username: admin password: 123456 # 连接池配置 clientName: soms-task # 客户端的标识,用于定位请求来源等 connectionTimeoutMs: 10000 # TCP连接超时,毫秒 readTimeoutMs: 15000 # TCP读取超时,毫秒 poolMaxWaitTimeMs: 3000 #当连接池无可用连接时客户端阻塞等待的时长,单位毫秒 connectionMaxIdleTimeMs: 60000 #TCP连接闲置时间,单位毫秒 connectionMaxLifeTimeMs: 120000 #TCP连接最多可以使用多久,单位毫秒 heartbeatFrequencyMs: 20000 #心跳检测发送频率,单位毫秒 minHeartbeatFrequencyMs: 8000 #最小的心跳检测发送频率,单位毫秒 heartbeatConnectionTimeoutMs: 10000 #心跳检测TCP连接超时,单位毫秒 heartbeatReadTimeoutMs: 15000 #心跳检测TCP连接读取超时,单位毫秒 connectionsPerHost: 100 # 每个host的TCP连接数 minConnectionsPerHost: 5 #每个host的最小TCP连接数 #计算允许多少个线程阻塞等待可用TCP连接时的乘数,算法: threadsAllowedToBlockForConnectionMultiplier*connectionsPerHost,当前配置允许10*20个线程阻塞 threadsAllowedToBlockForConnectionMultiplier: 10
配置类
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.List; @Data @Validated @Component @ConfigurationProperties(prefix = "mongodb") public class MongoClientOptionProperties { /** 基础连接参数 */ private String database; private String username; private String password; @NotNull private List<String> address; /** 客户端连接池参数 */ @NotNull @Size(min = 1) private String clientName; /** socket连接超时时间 */ @Min(value = 1) private int connectionTimeoutMs; /** socket读取超时时间 */ @Min(value = 1) private int readTimeoutMs; /** 连接池获取链接等待时间 */ @Min(value = 1) private int poolMaxWaitTimeMs; /** 连接闲置时间 */ @Min(value = 1) private int connectionMaxIdleTimeMs; /** 连接最多可以使用多久 */ @Min(value = 1) private int connectionMaxLifeTimeMs; /** 心跳检测发送频率 */ @Min(value = 2000) private int heartbeatFrequencyMs; /** 最小的心跳检测发送频率 */ @Min(value = 300) private int minHeartbeatFrequencyMs; /** 计算允许多少个线程阻塞等待时的乘数,算法:threadsAllowedToBlockForConnectionMultiplier*connectionsPerHost */ @Min(value = 1) private int threadsAllowedToBlockForConnectionMultiplier; /** 心跳检测连接超时时间 */ @Min(value = 200) private int heartbeatConnectionTimeoutMs; /** 心跳检测读取超时时间 */ @Min(value = 200) private int heartbeatReadTimeoutMs; /** 每个host最大连接数 */ @Min(value = 1) private int connectionsPerHost; /** 每个host的最小连接数 */ @Min(value = 1) private int minConnectionsPerHost;
连接池配置
import com.mongodb.MongoClient; import com.mongodb.MongoClientOptions; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoDbFactory; import org.springframework.data.mongodb.core.convert.*; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import java.util.ArrayList; import java.util.List; @Configuration public class MongoConfig { private final Logger log = LoggerFactory.getLogger(MongoConfig.class); /** * 自定义mongo连接池 * * @param properties * @return */ @Bean @Autowired public MongoDbFactory mongoDbFactory(MongoClientOptionProperties properties) { //创建客户端参数 MongoClientOptions options = mongoClientOptions(properties); //创建客户端和Factory List<ServerAddress> serverAddresses = new ArrayList<>(); for (String address : properties.getAddress()) { String[] hostAndPort = address.split(":"); String host = hostAndPort[0]; int port = Integer.parseInt(hostAndPort[1]); ServerAddress serverAddress = new ServerAddress(host, port); serverAddresses.add(serverAddress); } String username = properties.getUsername(); String password = properties.getPassword(); String database = properties.getDatabase(); MongoClient mongoClient; if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(password)) { //创建认证客户端 MongoCredential mongoCredential = MongoCredential.createScramSha1Credential( username, database, password.toCharArray()); mongoClient = new MongoClient(serverAddresses.get(0), mongoCredential, options); } else { //创建非认证客户端 mongoClient = new MongoClient(serverAddresses, options); } SimpleMongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient, database); log.info("mongodb注入成功"); return mongoDbFactory; } @Bean(name = "mongoTemplate") @Autowired public MongoTemplate getMongoTemplate(MongoDbFactory mongoDbFactory) { return new MongoTemplate(mongoDbFactory); } /** * mongo客户端参数配置 * * @return */ public MongoClientOptions mongoClientOptions(MongoClientOptionProperties properties) { return MongoClientOptions.builder() .connectTimeout(properties.getConnectionTimeoutMs()) .socketTimeout(properties.getReadTimeoutMs()).applicationName(properties.getClientName()) .heartbeatConnectTimeout(properties.getHeartbeatConnectionTimeoutMs()) .heartbeatSocketTimeout(properties.getHeartbeatReadTimeoutMs()) .heartbeatFrequency(properties.getHeartbeatFrequencyMs()) .minHeartbeatFrequency(properties.getMinHeartbeatFrequencyMs()) .maxConnectionIdleTime(properties.getConnectionMaxIdleTimeMs()) .maxConnectionLifeTime(properties.getConnectionMaxLifeTimeMs()) .maxWaitTime(properties.getPoolMaxWaitTimeMs()) .connectionsPerHost(properties.getConnectionsPerHost()) .threadsAllowedToBlockForConnectionMultiplier( properties.getThreadsAllowedToBlockForConnectionMultiplier()) .minConnectionsPerHost(properties.getMinConnectionsPerHost()).build(); } @Bean public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context, BeanFactory beanFactory) { DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory); MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context); try { mappingConverter.setCustomConversions(beanFactory.getBean(MongoCustomConversions.class)); } catch (NoSuchBeanDefinitionException ignore) { } // Don"t save _class to dao mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null)); return mappingConverter; } }
五、问题集
1、mongodb 3.0认证问题
MongoDB’s implementation of SCRAM-SHA-1 represents an improvement in security over the previously-used MONGODB-CR, providing: * A tunable work factor (iterationCount), * Per-user random salts rather than server-wide salts, * A cryptographically stronger hash function (SHA-1 rather than MD5), and * Authentication of the server to the client as well as the client to the server.
MongoDB 3.0新增了一种认证机制(authenticationMechanisms) SCRAM-SHA-1, 并把他设置为默认的方式. 而Spring Boot里默认使用旧的认证机制. 这就造成了不一致从而认证通不过. 解决方法有两种:
(1) 把Mongodb的认证机制改了: mongodb支持如下几种:
1 2 3 4 5 | SCRAM-SHA- 1 MONGODB-CR MONGODB-X509 GSSAPI (Kerberos) PLAIN (LDAP SASL) |
把Mongodb的认证方式改变一下自然能解决问题. 可以同时支持多个.
1 2 3 4 | setParameter: authenticationMechanisms: MONGODB-CR,SCRAM-SHA- 1 enableLocalhostAuthBypass: false logLevel: 4 |
但是既然MongoDB从3.0开始用SCRAM-SHA-1作为默认,应该是有道理的, 比如安全性方面比MONGODB-CR更好之类的.
(2)改变认证方式, 就只能改java代码了 我们看一下Spring Boot的源码; org.springframework.boot.autoconfigure.mongo.MongoProperties 的 createMongoClient方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public MongoClient createMongoClient(MongoClientOptions options) throws UnknownHostException { try { if (hasCustomAddress() || hasCustomCredentials()) { if (options == null ) { options = MongoClientOptions.builder().build(); } List<MongoCredential> credentials = null ; if (hasCustomCredentials()) { String database = this .authenticationDatabase == null ? getMongoClientDatabase() : this .authenticationDatabase; credentials = Arrays.asList(MongoCredential.createMongoCRCredential( this .username, database, this .password)); } String host = this .host == null ? "localhost" : this .host; int port = this .port == null ? DEFAULT_PORT : this .port; return new MongoClient(Arrays.asList( new ServerAddress(host, port)), credentials, options); } // The options and credentials are in the URI return new MongoClient( new MongoClientURI( this .uri, builder(options))); } finally { clearPassword(); } } |
可以看到它是用MongoCredential.createMongoCRCredential方法来创建认证信息, 并且没有留出任何公开的接口让你改变这一行为. 这个真是不应该呀. 找到问题所在, 其实解决就非常方便了, 使用createScramSha1Credential方法既可.
首先创建一个MongoDBConfiguration类, 用于创建MongoClient实例.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @Configuration @EnableConfigurationProperties (MongoProperties. class ) public class MongoDBConfiguration { @Autowired private MongoProperties properties; @Autowired (required = false ) private MongoClientOptions options; private Mongo mongo; @PreDestroy public void close() { if ( this .mongo != null ) { this .mongo.close(); } } @Bean public Mongo mongo() throws UnknownHostException { this .mongo = this .properties.createMongoClient( this .options); return this .mongo; } } |
2、解决SpringBoot MongoDB插入文档默认生成_class字段问题
@Configuration
public class SpringMongoConfig{
@Bean
public MongoTemplate mongoTemplate() throws Exception {
//remove _class
MappingMongoConverter converter =
new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);
return mongoTemplate;
}
}
3、spring boot 集成mongodb 开启事务
@Configuration
public class TransactionConfig {
@Bean
MongoTransactionManager transactionManager(MongoDbFactory factory){
return new MongoTransactionManager(factory);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具