Nosql学习

NoSql学习

1.MongoDB

1.数据库介绍

#1.什么是MongoDB?默认端口27017
MongoDB是C++编写的一个文档数据库(以JSON为数据模型),旨在为WEB应用提供可拓展的高性能数据存储解决方案
它是一个介于关系数据库和非关系数据库之间的产品,原则上mysql能做的它也能做
##2.mongodb概念
	database: 数据库。
	collection: 数据集合,相当于 MySQL 的 table。
	document: 数据记录行,相当于 MySQL 的 row。
	field: 数据域,相当于 MySQL 的 column。
	index: 索引。
	primary key: 主键。
##3.特点
	1.面向集合存储:MongoDB 是面向集合的,数据以 collection 分组存储。每个 collection 在数据库中都有唯一的名称
	2.模式自由:集合的概念类似 MySQL 里的表,但它不需要定义任何模式
	3.结构松散:对于存储在数据库中的文档,不需要设置相同的字段,并且相同的字段不需要相同的数据类型,不同结构的文档可以存在同一个 collection 里
	4.高效的二进制存储:存储在集合中的文档,是以键值对的形式存在的。键用于唯一标识一个文档,一般是 ObjectId 类型,值是以 BSON 形式存在的。BSON = Binary JSON, 是在 JSON 基础上加了一些类型及元数据描述的格式
	5.支持索引:可以在任意属性上建立索引,包含内部对象。MongoDB 的索引和 MySQL 的索引基本一样,可以在指定属性上创建索引以提高查询的速度。除此之外,MongoDB 还提供创建基于地理空间的索引的能力
	6.支持 mapreduce:通过分治的方式完成复杂的聚合任务
	7.支持 failover:通过主从复制机制,可以实现数据备份、故障恢复、读扩展等功能。基于复制集的复制机制提供了自动故障恢复的功能,确保了集群数据不会丢失
	8.支持分片:MongoDB 支持集群自动切分数据,可以使集群存储更多的数据,实现更大的负载,在数据插入和更新时,能够自动路由和存储
	9.支持存储大文件:MongoDB 中 BSON 对象最大不能超过 #####16 MB。
	对于大文件的存储,BSON 格式无法满足。GridFS 机制提供了一个存储大文件的机制,可以将一个大文件分割成为多个较小的文档进行存储
##4.应用场景
	从目前阿里云 MongoDB云数据库上的用户看,MongoDB 的应用已经渗透到各个领域 
	●游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新; 
	•物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以MongoDB 内嵌数组的形式来存储,一次查询就能 将订单所有的变更读取出来; 
	•社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能; 
	●物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析; 	•视频直播,使用 MongoDB 存储用户信息、礼物信息等; • 大数据应用,使用云数据库MongoDB作为大数据的云存储系统,随时进行数据提取分析,掌握行业动态。| 国内外知名互联网公司都在使用MongoDB
###5.是否使用MongoDB? 
	没有某个业务场景必须要使用MongoDB才能解决,但使用MongoDB通常能让你以更低的成本解决问题。如果你不清楚当前业务是否适合使用MongoDB,可以通过做几道选择题来辅助决策。 
	应用特征 应用不需要复杂/长事务及join支持 ###yes使用
	新应用,需求会变,数据模型无法确定,想快速迭代开发 ###可用
	应用需要2000-3000以上的读写QPS(更高也可以) ###可用
	应用需要TB甚至 PB 级别数据存储 ###可用
	应用发展迅速,需要能快速水平扩展 ###可用
	应用要求存储的数据不丢失 ###可用
	应用需要99.999%高可用 ###可用
	应用需要大量的地理位置查询、文本查询###可用
###6.下载安装mongodb(具体可以百度Linux安装MongoDB)
本人使用docker安装
	 docker run -d --restart=always -p 27017:27017
     --name mongo 
     -v /home/lmy/mongodb:/data/db 
     --privileged=true 
     -e MONGO_INITDB_ROOT_USERNAME=root #账号
     -e MONGO_INITDB_ROOT_PASSWORD=root #密码
     mongo:latest mongod 
     --auth #默认以非授权方式启动;以授权模式启动
MongoDB常见类型说明
Object ID文档ID
String字符串,最常用,必须是有效的UTF-8
Boolean存储一个布尔值,true或false
Integer整数可以是32位或64位,这取决于服务器
Double存储浮点值
Arrays数组(js)或列表(python),多个值存储到一个键
Object用于嵌入式的文档,即一个值为一个文档
Null存储Null值
Timestamp时间戳
Date存储当前日期或时间的UNIX时间格式

2.基本操作及命令

#1.mongoDB shell使用
	控制台使用mongo --host=127.0.0.1 --port=27017
	JavaScript支持:mongo shell是基于JS语法的,interpreterVersion()查看支持的js的版本
	#常用shell命令
    show users				#显示当前数据库的用户列表
    show roles				#显示当前数据库的角色列表
    show profile			#显示最近发生的操作
    load("xxx.js") 			#执行一个JavaScript脚本文件
    exit | quit() 		#退出当前shell 
    help				#查看mongodb支持哪些命令
    db.help()			#查询当前数据库支持的方法
   
    #数据库相关
    db.version() 		#查看数据库版本
    show dbs | show databases	#显示数据库列表
    use 数据库名			#切换数据库,如果不存在创建数据库
    db.dropDatabase()		#删除数据库
    #集合相关
    db.集合名.help()		#显示集合的帮助信息
    show collections | show tables	#查看集合列表
    db.createCollection("aaa")	#创建集合
    db.集合名.stats()		#查看集合详情
    db.集合名.drop() 		#删除集合
    db.createCollection("aaa",options)	#创建集合 当集合不存在时,向集合中插入文档也会创建集合
    	#options参数
    	capped 布尔类型 (可选)如果为true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 
    	size 数值类型 (可选)为固定集合指定一个最大值(以字节计)。如果 capped为 true,也需要指定该字段。
         max 数值类型 (可选)指定固定集合中包含文档的最大数量。
    #用户
    db.createUser({user:"admin",pwd:"admin",roles:["dbOwner"]})#创建admin/admin的用户
    #常用权限
    read 				#允许用户读取指定数据库
    readWrite			#允许用户读写指定数据库
    dbAdmin 			#允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
    dbOwner				# 允许用户在指定数据库中执行任意操作,增、删、改、查等 
    userAdmin			#允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户 
    clusterAdmin 		#只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限 
    readAnyDatabase		#只在admin数据库中可用,赋予用户所有数据库的读权限 
    readWriteAnyDatabase#只在admin数据库中可用,赋予用户所有数据库的读写权限
    userAdminAnyDatabase#只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
    dbAdminAnyDatabase	#只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限 
    root 				#只在admin数据库中可用。超级账号,超级权限
######2.MongoDB文档操作
	1) 插入文档 (3.2版本之后新增了db.collection.insertOne()和db.collection.insertMany())
		#a) insertOne:支持writeConcern
			writeConcern 决定一个写操作落到多少个节点上才算成功。
			writeConcern 的取值包括 : 
			0:发起写操作,不关心是否成功; 
			1:集群最大数据节点数 : 写操作需要被复制到指定节点数才算成功;
            majority : 写操作需要被复制到大多数节点上才算成功。
         #b)insert:若插入的数据主键已经存在,则会抛 DuplicateKeyException 异常,提示主键重复,不保存当前数据。 
         #c)save:如果 _id 主键存在则更新数据,如果不存在就插入数据。
         #d)insertMany:向指定集合插入多条文档数据
         	writeConcern : 写入策略,默认为1,即要求确认写操作, 是不要求。 
         	ordered : 指定是否按顺序写入,默认 true,按顺序写入。
         #e)insert和save也可以实现批量插入
         f)load("xxx.js")js中写插入命令等也可以实现批量插入
      2)查询文档
      	#db.集合名.find(query,projection) | db.getCollection("集合名").find(query,projection)
      	#例如:db.student.find({"age":{$lt:23}},{id:"",name:"",major:""})
      		a)query:可选,使用查询操作符指定查询条件 
      		b)projection:可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可 (默认省略)。投影时,id 为1的时候,其他字段必须是1;id是0的时候,其他字段可以是0;如果没有_id字段约束,多个其他字段必须同为0或同为1。
      		c)如果查询返回的条目数量较多,mongo shell则会自动实现分批显示。
      		默认情况下每次只显示20条,可以输入it命令读取下一批。
        #db.集合名.findone(query, projection) findOne查询集合中的第一个文档。
       	########################条件查询############################################
       	#查询带有nosql标签的book文档 :
        db.books.find({tag:"nosql"})
        #按照id查询单个book文档 : 
        db.books.find({_id:ObjectId("61caa09ee0782536660494d9")})
        #查询分类为“travel”、收藏数超过60个的book文档
        db.books.find({type:"travel",favCount:{$gt:60}})
        ####查询条件对比
        SQL 			MQL
        a = 1 			{a : 1}
        a <> 1 			{a : ($ne : 1}}
        a> 1 			{a : {$gt : 1}}
        a>= 1 			{a : {$gte : 1}} 
        a < 1 			{a : {$lt : 1}}
        a <= 1      	{a : {$Ite : 1}}
        ####查询逻辑对比
        SQL 				MQL
        a = 1 AND b=1 		{a:1, b:1}{$and : [{a:1}, {b:1}]}
        a = 1 OR b = 1 		{$or : [{a : 1}, {b : 1}]}
        a IS NULL 			{a : {$exists : false}}
        a IN (1, 2, 3)     	 {a : {$in : [1, 2, 3]}}
        ####查询逻辑运算符
        $lt : 存在并小于 
        $lte : 存在并小于等于 
        $gt : 存在并大于 
        $gte : 存在并大于等于 
        $ne : 不存在或存在但不等于 
        $in : 存在并在指定数组中
        ###排序&分页
        指定排序 在 MongoDB 中使用 sort() 方法对数据进行排序 
        #指定按收藏数(favCount)降序返回  #1为升序, -1为降序
        db.books.find({type : "travel"}).sort({favCount : -1}) 
        分页查询 skip用于指定跳过记录数,limit则用于限定返回结果数量。可以在执行find命令的同时指定skip、limit参数,以此实现分页的功能;limit是pageSize,skip是第几页*pageSize。
        #假定每页大小为8条,查询第3页的book文档 
        db.books.find().skip(8).limit(4)
        #正则表达式匹配查询 MongoDB 使用 $rege 操作符来设置匹配字符串的正则表达式。 
        使用正则表达式查找type包含so字符串的book
        db.books.find({type:{$regex:"so"}}) 或 db.books.find({type:/so/})
  3)更新文档  可以用update命令对指定的数据进行更新
  		#db.collection.update(query,update,options) 
  			query : 描述更新的查询条件; 
             update : 描述更新的动作及新的内容; 
  			options : 描述更新的选项 
  				upsert : 可选,如果不存在update的记录,是否插入新的记录。默认false,不插入 
  				multi可选,是否按条件查询出的多条记录全部更新。 默认false,只更新找到的第一条记录
            	 writeConcern 可选,决定一个写操作落到多少个节点上才算成功
       #更新操作符 
       操作符			格式										描述
       $set			{$set : {field : value}}		#指定一个键并更新值,若键不存在则创建
       $unset		{$unset : {field : 1}}			#删除一个键 
       $inc			{$inc : {field : value}}		#对数值类型进行增减
       $rename		{$rename : {old_field_name : new_field_name}}	#修改字段名称
       $push 		{ $push : {field : value}}		#将数值追加到数组中,若数组不存在则会进行初始化
       $pfishall	 {$pushAll : {field : value_array}} 	#追加多个值到一个数组字段内
       $pull 		 {$pull : {field : value}}		#从数组中删除指定的元素
       $addToSet 	 {$addToSet : {field : value}}	#添加元素到数组中,具有排重功能
       $pop          {$pop : {field : 1}} 			#删除数组的第一个或最后一个元素
   	  #####更新单个文档
   	  	某个book文档被收藏了,则需要将该文档的favCount字段自增 
   	  	db.books.update({_id:ObjectId("61caa09ee0782536660494d9")},{Sinc:{favCount:1}})
   	  #####更新多个文档
   	  	默认情况下,update命令只在更新第一个文档之后返回,如果需要更新多个文档,则可以使用multi选项。 
   	  	将分类为“novel”的文档的增加发布时间(publishedDate) 
   	  	db.books.update({type : "novel"},{$set:{publishedDate : new Date()}},{"multi":true}) 					multi:可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新	
   	  	#update命令的选项配置较多,为了简化使用还可以使用一些快捷命令
   	  		updateOne:更新单个文档。
            updateMany:更新多个文档。
            replaceOne:替换单个文档。
         #upsert命令 upsert是一种特殊的更新,其表现为如果目标文档不存在,则执行插入命令。
         	db.books.update({title : "my book"}, 
         {$set : {tags : ["nos q1", "mongodb"], type : "none", author : "fox"}},
         {upsert : true})
         	nMatched、nModified都为0,表示沒有文档被匹配及更新,nUpserted=1提示执行了upsert动作
         #replace语义
         	update命令中的更新描述(update)通常由操作符描述,如果更新描述中不包含任何操作符,那么MongoDB会实现		文档的replace语义
            db.books.update({title : "my book"}, {justTitle : "my first book"})
         #findAndModify命令 
         	findAndModify兼容了查询和修改指定文档的功能,findAndModify只能更新单个文档
         	#将某个book文档的收藏数(favCount)1  该操作会返回符合查询条件的文档数据,并完成对文档的修改。
         	db.books.findAndModify({query:{_id : ObjectId("61caa09ee07825 36660494dd") }, 
         	update : {$inc : {favCount : 1}}
         	})
         	#默认情况下,findAndModify会返回修改前的“旧”数据。如果希望返回修改后的数据,则可以指定new选项
         	db.books.findAndModify({query:{_id : ObjectId("61caa09ee0782536660494dd") }, 
         	update:{$inc : {favCount : 1}}, 
         	new : true
         	})
         	#与findAndModify语义相近的命令如下 
         	findoneAndUpdate : 更新单个文档并返回更新前(或更新后)的文档。 
         	findOneAndreplace : 替换单个文档并返回替换前(或替换后)的文档
  4)删除文档   #ps: remove、deleteMany等命令需要对查询范围内的文档逐个删除,如果希望删除整个集合,则使用drop命令会更加高效
  		#使用 remove 删除文档 
  		remove 命令需要配合查询条件使用; 
  		匹配查询条件的文档会被删除; 
  		指定一个空文档条件会删除所有文档;
  		db.user.remove({age : 28})	#删除age=28的记录
  		db.user.remove({age : {$1t : 25}})	#删除age小于25的记录
        db.user.remove({}) #删除所有记录
        db.user.remove()	#报错
        remove命令会删除匹配条件的全部文档,如果希望明确限定只删除一个文档,则需要指定justOne参数
        db.集合名.remove(query, justone)
        #删除满足type : novel条件的首条记录 
        db.books.remove({ type : "novel"},true)
        
        #使用delete删除文档 官方推荐deleteOne()和deleteMany()方法删除文档
        db.books.deleteMany({}) #删除集合下全部文档
        db.books.deleteMany({ type : "novel" }) #删除type为novel的文档
        db.books.deleteOne({ type : "novel" })	#删除type为novel的一个文档
       #返回被删除文档
       	remove、deleteOne等命令在删除文档后只会返回确认性的信息,如果希望获得被删除的文档,则可以使用findOneAndDelete命令
       	db.books.findOneAndDelete({type:"nove1"})
       	除了在结果中返回删除文档,findOneAndDelete命令还允许定义“删除的顺序”,即按照指定顺序删除找到的第一个文档 
       	db.books.findoneAndDelete({type : "nove1"},{sort : {favCount : 1}})
        remove、deleteOne等命令只能按默认顺序删除,利用这个特性,findOneAndDelete可以实现队列的先进先出。 
#####################样例mql##############
   ##统计学生总数 
        db.student.count()
   ##统计历史专业的学生人数
		db.student.count({"major":"Histroy"})
	##统计各个专业的人数
		db.student.aggregate([{$group:{_id:"$major","人数":{$sum:1}}}])
	##统计每个专业的学生人数,并求他们的平均年龄
		db.student.aggregate([{$group:{_id:"$major","学生人数":{$sum:1},"平均年龄":{$avg:"$age"}}}])
	##查询男生与女生的平均年龄
		db.student.aggregate([{$group:{_id:"$sex","平均年龄":{$avg:"$age"}}}])
	##将指定机器上的数据库的数据克隆到当前数据库
	db.cloneDatabase(127.0.0.1); 
	##显示当前db状态
		db.stats();
	##当前db版本
		db.version();
	##查询当前聚集集合所有索引
		db.userInfo.getIndexes();
	##查看总索引记录大小
		db.userInfo.totalIndexSize();
	##删除指定索引
		db.users.dropIndex("name_1");
	##删除所有索引
		db.users.dropIndexes();
分组函数说明
$sum计算总和,$sum:1同count表示计数
$avg计算平均值
$min获取最小值
$max获取最大值
$push在结果文档中插入值到一个数组中,相当于拼接字段
$first根据资源文档的排序获取第一个文档数据
$last根据资源文档的排序获取最后一个文档数据

3.进阶

######MongoDB数据库
MongoDB副本集默认会创建local、admin数据库,local数据库主要存储副本集的元数据,admin数据库则主要存储MongoDB的用户、角色等信息。
1.慎用local数据库
	从名字可以看出,它只会在本地存储数据,即local数据库里的内容不会同步到副本集里其他节点上去;目前local数据库主要存储副本集的配置信息、oplog信息,这些信息是每个Mongod进程独有的,不需要同步到副本集种其他节点
2.慎用admin数据库
	当Mongod启用auth选项时,用户需要创建数据库帐号,访问时根据帐号信息来鉴权,而数据库帐号信息就存储在admin数据库下;MongoDB将admin数据库上的意向写锁(MODE_IX)直接升级为写锁(MODE_X),也就是说admin数据库的写入操作的锁级别只能到DB级别,不支持多个collection并发写入,在写入时也不支持并发读取。如果用户在admin数据库里存储业务数据,则可能遭遇性能问题
3.config数据库
	当 MongoDB 使用分片设置时,config 数据库可用来保存分片的相关信息
#####MongoDB 索引
	1.建立索引 #MongoDB默认会为集合创建_id字段的索引
		db.集合名.createIndex( {age: 1} )  #按age字段创建升序索引
	2.MongoDB索引类型
	    db.person.createIndex( {age: 1, name: 1} ) #复合索引
		MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场合。
	2.1 多key索引 (Multikey Index)
		当索引的字段为数组时,创建出的索引称为多key索引,多key索引会为数组的每个元素建立一条索引
		{"name" : "jack", "age" : 19, habbit: ["football, runnning"]}
		db.person.createIndex( {habbit: 1} )  // 自动创建多key索引
		db.person.find( {habbit: "football"} )
	2.2 其他类型索引
			哈希索引(Hashed Index)是指按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。
			地理位置索引(Geospatial Index)能很好的解决O2O的应用场景,比如『查找附近的美食』、『查找某个区域内的车站』等。
			文本索引(Text Index)能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则可以针对博客内容建立文本索引
	2.3 索引额外属性
		MongoDB除了支持多种不同类型的索引,还能对索引定制一些特殊的属性。
			唯一索引 (unique index):保证索引对应的字段不会出现相同的值,比如_id索引就是唯一索引
			TTL索引:可以针对某个时间字段,指定文档的过期时间(经过指定时间后过期 或 在某个时间点过期)
			部分索引 (partial index): 只针对符合某个特定条件的文档建立索引,3.2版本才支持该特性
			稀疏索引(sparse index): 只针对存在索引字段的文档建立索引,可看做是部分索引的一种特殊情况
	2.4 索引优化  db profiling
		MongoDB支持对DB的请求进行profiling,目前支持3种级别的profiling。
			0:不开启profiling
			1:将处理时间超过某个阈值(默认100ms)的请求都记录到DB下的system.profile集合 (类似于mysql、redis的slowlog)
			2:将所有的请求都记录到DB下的system.profile集合(生产环境慎用)
	#3.索引计划
	db.collection.explain() 
		explain有三种模式,分别是:①queryPlanner、②executionStats、③allPlansExecution
		#重要的关键字
		1.COLLSCAN:代表该查询进行了全表扫描;
		2.IXSCAN:代表进行了索引扫描;
		3.keysExamined:代表索引扫描条目;
		4.docsExamined:代表文档扫描条目。

4.Springboot集成MongoDB

#1.引入依赖
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-mongodb</artifactId>
     <version>2.0.1.RELEASE</version>
 </dependency>
#2.配置文件
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
#3.连接池配置
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
1.工具类
//工具类
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);
        }
    }
}
2.配置类
//配置类
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;
3.连接池配置
/连接池配置
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;
 }
}
4.集成问题
1.mongodb 3.0认证问题
    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 
    2)改变Springboot认证方式
         org.springframework.boot.autoconfigure.mongo.MongoProperties 的 createMongoClient方法
                
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);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没有什么是应该

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值