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);
    }

}
复制代码

六、其他学习资料参考

 

posted on   书梦一生  阅读(812)  评论(0编辑  收藏  举报

编辑推荐:
· 开发者必知的日志记录最佳实践
· 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工具
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示