• spring boot 集成mongodb


    一、相关依赖

           <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-mongodb</artifactId>
                <version>2.0.1.RELEASE</version>
            </dependency>

    二、配置文件

    spring.data.mongodb.uri=mongodb://adminUser:adminPass@localhost:27017/?authSource=admin&authMechanism=SCRAM-SHA-1
    spring.data.mongodb.database=users
    多个 IP 集群可以采用以下配置:
    spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/databases

    三、基本操作方法封装

    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支持如下几种:

    SCRAM-SHA-1	
    MONGODB-CR
    MONGODB-X509
    GSSAPI (Kerberos)
    PLAIN (LDAP SASL)  

    把Mongodb的认证方式改变一下自然能解决问题. 可以同时支持多个.

    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方法:

    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实例.

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

    六、其他学习资料参考

     
  • 相关阅读:
    1.3 Starting a New Job 1.3.3 Background Reading(I)
    1.3 Starting a New Job 1.3.1 Preparation
    1.2 Interview 1.2.4 Sample Test(IV)
    1.2 Interview 1.2.4 Sample Test(III)
    Jquery获取元素集合并遍历
    Oracle21c RAC+DG生产项目实战(RHEL8+Oracle集群+容灾+CDB/PDB)
    Django
    Linux 实用命令
    Python人工智能:原理、实践及应用-资料分享
    关于HDFS的NameNode和SecondaryNameNode的一些疑问解答
  • 原文地址:https://www.cnblogs.com/yuarvin/p/14654863.html
Copyright © 2020-2023  润新知