标题:mongoDB操作应用万能手册,熟练不等于精通;
引言:日常工作的各种查询语句总结,自动化安装部署,应用接入……
1、操作篇
客户端连接mongo服务,身份认证之后才能操作,身份认证之前必须切换到admin库,身份认证函数使用db.auth(user, password)
;
root@VM-218-88-centos:/# mongo
MongoDB shell version v4.0.4
connecting to: mongodb://127.0.0.1:27017
Implicit session: session { "id" : UUID("9f2bb75f-783e-4e57-a5d2-8c5301005c61") }
MongoDB server version: 4.0.4
> use admin
switched to db admin
> db.auth("user","password")
1
1.1、数据库
1、新增(切到)目标数据库:use database
2、查看当前数据库:db
;
3、查看所有的数据库:show databases
或者show dbs
;
4、删除数据库,先use database
,切换到需要删除的数据库,然后执行db.dropDatabase()
删除当前库;
> use vendors
switched to db vendors
> db
vendors
> db.dropDatabase()
{ "dropped" : "vendors", "ok" : 1 }
自定义的用户可能会报没有删除权限操作,需要添加一下root角色权限;
1.2、集合
1、查看当前库中的所有集合:show collections
或者 show tables
;
2、创建集合(可指定配置参数):db.createCollection(colName, options)
,可选项参数如下;
- capped:(可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数;
- size:(可选)为固定集合指定一个最大值,即字节数。 如果 capped 为 true,也需要指定该字段;
- max:(可选)指定固定集合中包含文档的最大数量;
当操作的集合不存在时,直接向其插入文档,也会自动的创建集合;
3、删除集合:db.colName.drop()
;
> use test
switched to db test
> db.createCollection("user")
{ "ok" : 1 }
> show tables
user
> db.user.drop()
true
> show tables
1.3、文档
1.3.1、增
单条插入
db.colName.insert(obj)
> db.user.insert({"name":"ikejcwang", "age":27})
WriteResult({ "nInserted" : 1 })
多条插入
db.colName.insertMany(arr, {writeConcern: 1, ordered: true})
- arr:需要插入的条目数组,(不要求数组元素的字段一致,基于mongo的特性);
- options:可选项扩展;
- writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求;
- ordered:指定是否按数组的顺序写入,默认 true,按顺序写入;
> db.user.insertMany([{name:"i", age:23}, {name:"k", age:24}, {name:"e", age:25}], {ordered: false})
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("633d2a67b242067655f426b6"),
ObjectId("633d2a67b242067655f426b7"),
ObjectId("633d2a67b242067655f426b8")
]
}
同时,mongo支持JS脚本语句操作(任何场景下都行):
> for(let i = 0; i <= 5; i++){
... db.user.insert({name:"ike"+i, age:30+i})
... }
WriteResult({ "nInserted" : 1 })
1.3.2、删
db.colName.remove(<query>, {justOne: false, writeConcern: 1})
;
- query:,删除的指定条件(全删,传空即可);
- justOne:(可选),如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配的文档;
- writeConcern:(可选),抛出异常级别
> db.user.remove({name:"ike0"})
WriteResult({ "nRemoved" : 1 })
>
> db.user.remove({})
WriteResult({ "nRemoved" : 9 })
1.3.3、改
db.colName.update(<query>, <update>, {upsert: false, multi: false, writeConcern: 1})
;
- query:修改的查询条件(全修改传空即可);
- update:更新覆盖的内容;
- upsert:(可选)如果不存在update的记录,是否插入update,true为插入,默认是false,不插入;
- multi:(可选)默认是false,只更新找到的第一条记录,设置参数为true,就把按条件查出来多条记录全部更新;
- writeConcern:(可选),抛出异常的级别
更新操作必须使用修改器$set,query为空配合{multi: true},表示修改全部
> db.user.update({}, {"$set": {name:"ikejcwang"}}, {multi: true})
WriteResult({ "nMatched" : 6, "nUpserted" : 0, "nModified" : 5 })
1.3.4、查
查询后面继续调用pretty()
函数,可以格式化结果集展示
标准
db.colName.find(<query>, projection)
- query:可选:查询条件(查询条件多样性),
projection:展示和屏蔽的字段。eg:{name: 1}结果集仅展示name字段,{name: 0}结果集屏蔽name字段;
_id会默认展示出来,除非手动屏蔽
> db.user.find({name:"ikejcwang"}, {name:1})
{ "_id" : ObjectId("633d2e90b242067655f426c0"), "name" : "ikejcwang" }
{ "_id" : ObjectId("633d2e90b242067655f426c1"), "name" : "ikejcwang" }
{ "_id" : ObjectId("633d2e90b242067655f426c2"), "name" : "ikejcwang" }
{ "_id" : ObjectId("633d2e90b242067655f426c3"), "name" : "ikejcwang" }
{ "_id" : ObjectId("633d2e90b242067655f426c4"), "name" : "ikejcwang" }
> db.user.find({name:"ikejcwang"}, {name:0, _id:0})
{ "age" : 31 }
{ "age" : 32 }
{ "age" : 33 }
{ "age" : 34 }
{ "age" : 35 }
统计
db.colName.find().size()
或者
db.colName.find().count()
;
去重
db.colName.distinct(field)
;
- field:字符串,去重筛选的字段,
> db.user.distinct("name")
[ "ikejcwang" ]
排序
db.colName.find().sort(fieldOptions)
;
- fieldOptions:支持多字段排序,1:生序,-1:降序。
> db.user.find().sort({name:1, age:-1})
模糊
,模糊如下:
正则表达式的匹配逻辑
# 查询集合中name字段,包含ik的条目
db.user.find({name:/ik/})
# 查询集合中name字段,以ik开头的条目
db.user.find({name:/^ik/})
# 查询集合中name字段,以ik结尾的条目
db.user.find({name:/ik$/})
大小于
,大于小于等于的条件需要用到比较器:$lt(小于),$lte(小于等于),$gt(大于),$gte(大于等于),$ne(不等于)。
# 查询age小于33的条目
> db.user.find({age:{$lt:33}})
# 查询age大于33的条目
> db.user.find({age:{$gt:33}})
# 查询age大于33,小于35的条目
> db.user.find({age:{$gt:33, $lt:35}})
存在判断
,需要用到操作符$exists
,主要是操作字段内容是否为空
- $exists:true,不为空,false,为空;
# 查询统计name不为空的条目个数
db.user.find(name:{$exists:true}).count()
# 类似于mysql的
select count(name) from tableName
or
,需要用到操作符$or,
# 单独or
db.tableName.find({"$or":[{"login_name":/王/},{"role_name":/王/}]},{"login_name":true,"role_name":true})
# and or,
db.tableName.find({"name":"ike", "$or":[{"login_name":/王/},{"role_name":/王/}]},{"login_name":true,"role_name":true})
分页
limit()
和skip()
函数
- limit:查询条目数;
- skip:查询的起始位置;
# 查询聚集中前5条记录
>db.tableName.find().limit(5)
# 查询聚集中第10条以后的记录,就是从11条开始
>db.tableName.find().skip(10)
# 查询聚集中第10条记录以后的5条记录,正式分页处理
>db.tableName.find().skip(10).limit(5)
包含与否
,需要用到操作符$in
,$nin
- $in:value为数组,表示查询引用字段的值包含在数组中的条目;
- $nin:value为数组,表示查询引用字段的值不包含在数组中的条目;
# 查询name的值,在给定的数组中的条目
db.user.find({name:{$in:["ikejcwang", "hah"]}})
# 查询name的值,不在给定的数组中的条目
db.user.find({name:{$nin:["hah"]}})
1.3.5、数组查询
构造测试的数组文档字段,如下所示:tag字段为数组
> for(let i = 0; i <= 10; i++){
... let tags = [];
... for(let t = 0; t <= i; t++){
... tags.push("tag"+t);
... }
... db.user.insert({name:"ik"+i, age:"30"+i, tag:tags})
... }
WriteResult({ "nInserted" : 1 })
>
> db.user.find()
{ "_id" : ObjectId("633d4434b242067655f426c5"), "name" : "ik0", "age" : "300", "tag" : [ "tag0" ] }
{ "_id" : ObjectId("633d4434b242067655f426c6"), "name" : "ik1", "age" : "301", "tag" : [ "tag0", "tag1" ] }
{ "_id" : ObjectId("633d4434b242067655f426c7"), "name" : "ik2", "age" : "302", "tag" : [ "tag0", "tag1", "tag2" ] }
{ "_id" : ObjectId("633d4434b242067655f426c8"), "name" : "ik3", "age" : "303", "tag" : [ "tag0", "tag1", "tag2", "tag3" ] }
{ "_id" : ObjectId("633d4434b242067655f426c9"), "name" : "ik4", "age" : "304", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4" ] }
{ "_id" : ObjectId("633d4434b242067655f426ca"), "name" : "ik5", "age" : "305", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4", "tag5" ] }
{ "_id" : ObjectId("633d4434b242067655f426cb"), "name" : "ik6", "age" : "306", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4", "tag5", "tag6" ] }
{ "_id" : ObjectId("633d4434b242067655f426cc"), "name" : "ik7", "age" : "307", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7" ] }
{ "_id" : ObjectId("633d4434b242067655f426cd"), "name" : "ik8", "age" : "308", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8" ] }
{ "_id" : ObjectId("633d4434b242067655f426ce"), "name" : "ik9", "age" : "309", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9" ] }
{ "_id" : ObjectId("633d4434b242067655f426cf"), "name" : "ik10", "age" : "3010", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tag10" ] }
匹配
要对数组字段进行相等条件的查询,条件肯定也必须为数组,且顺序也必须一致;
:
# 指定集合中,tag字段 == ["tag0", "tag1"]的条目
> db.user.find({tag:["tag0", "tag1"]})
{ "_id" : ObjectId("633d4434b242067655f426c6"), "name" : "ik1", "age" : "301", "tag" : [ "tag0", "tag1" ] }
包含
如果只是要找到一个包含指定元素的数组,但不考虑目标字段数组中的顺序或其他元素,需要使用$all
操作符
# 指定集合中,tag字段数组元素中,包含"tag0", "tag1"的条目数量
> db.user.find({tag:{$all:["tag0", "tag1"]}}).count()
10
元素查询
查询数组字段是否包含指定值的元素,使用普通的过滤器;
# 查询tag字段中,包含"tag9"元素的条目数量
> db.user.find({tag:"tag9"}).count()
2
同理,它也支持其他的条件过滤:比如大小于:
db.user.find({tag:{$gt: "10"}}).count()
;db.user.find({tag:{$gt: 10}}).count()
多条件元素查询
1、复合条件单个满足即可:
# 查询结果:目标字段数组中:单个元素满足大于15,或者单个元素满足小于 20,或者单个元素同时满足这两个条件的结果集:
db.user.find( { tagNum: { $gt: 15, $lt: 20 } } )
2、复合条件多个满足:
# 查询结果:目标字段数组中:至少包含一个元素能满足大于22,小于33的结果集
db.user.find( { dim_cm: { $elemMatch: { $gt: 22, $lt: 30 } } } )
3、按目标字段数据元素的索引位置匹配查询
.的形式
# 查询tag字段,第9个元素为"tag9"的
> db.user.find({"tag.9": "tag9"}).count()
2
4、按目标字段数组的长度查询
# 查询tag字段数组长度为5的条目
> db.user.find({tag:{$size:5}})
{ "_id" : ObjectId("633d4434b242067655f426c9"), "name" : "ik4", "age" : "304", "tag" : [ "tag0", "tag1", "tag2", "tag3", "tag4" ] }
1.3.6、聚合查询
aggregate
:聚合运算函数
- $match:查询过滤条件;
- $lookup:连表查询;
- $project:展示/屏蔽字段,采用$符号来定义别名;
- …………
字段别名
find
只能展示和屏蔽字段,但是不能给字段定义别名,这里只能依靠聚合操作;
# 查询name包含"ik"的条目,并且将name字段取别名为userName展示,
> db.user.aggregate([{$match:{name:/ik/}}, {$project:{"userName":"$name", "_id":0}}])
{ "userName" : "ikejcwang" }
{ "userName" : "ikejcwang" }
{ "userName" : "ikejcwang" }
{ "userName" : "ikejcwang" }
{ "userName" : "ikejcwang" }
{ "userName" : "ikejcwang" }
{ "userName" : "ik0" }
{ "userName" : "ik1" }
{ "userName" : "ik2" }
{ "userName" : "ik3" }
{ "userName" : "ik4" }
{ "userName" : "ik5" }
{ "userName" : "ik6" }
{ "userName" : "ik7" }
{ "userName" : "ik8" }
{ "userName" : "ik9" }
{ "userName" : "ik10" }
联合查询
使用$lookup
操作符,相当于连表join,$lookup参数介绍:
- from:需要连接的集合名称;
- as:对from的集合取别名;
- localField:当前集合中对from的关联字段;
- foreignField:from集合中关联当前集合的字段;
# 如下两张集合有关联关系,通过acs_policy的resource_member_ids来关联,
# 操作预期:acs_policy关联acs_resource_member查询,通过acs_policy的subjects字段做过滤查询,结果集屏蔽_id,将acs_policy的subjects取别名为role_ids,将关联acs_resource_member的id取别名为resource_member_id_list,最终对结果集格式化输出。
> db.acs_policy.aggregate([
{"$match":{"subjects":{"$in":["d466d7a9-5152-47e7-ba5c-1c9bb8d0ad6c", "62a0ca2d-413a-4484-ac08-b911595b70d9"]}}},
{"$lookup":{"from":"acs_resource_member", "as":"resourceMember", "localField":"resource_member_ids", "foreignField":"id"}},
{"$project": {"resource_member_id_list": "$resourceMember.id", "role_ids": "$subjects", "_id":0}},
]).pretty()
{
"resource_member_id_list" : [
"146804b5-95bb-444a-8bd8-811d0364866c",
"1c0c338f-b63f-48b5-9134-f48460d4ab10",
"1f69910a-71d0-4d02-995c-215816758b6c",
"c14b7e97-60cb-4a14-aa31-91ee542089b0",
"c774b0c4-2774-4fcf-8760-5b49951c12e8",
"dbc8d823-5a08-406f-8670-28ccb74ec959",
"f6e03c36-0f64-4b77-a6dc-f6ae4af76abc"
],
"role_ids" : [
"62a0ca2d-413a-4484-ac08-b911595b70d9"
]
}
{
"resource_member_id_list" : [
"10d514b6-38a5-4f1a-8f61-a5874c1788ef",
"3022c03e-bcac-4d98-8952-d118b581d803",
"3d2e3770-0d1c-402b-a7f7-f7f5a665f69d",
"96945cde-a5bb-4ba3-ae28-04f163bb296c",
"9b0b1146-ae05-46da-9a42-07d13e1169c7",
"ceb77748-39af-47e8-817d-282754058eed",
"f8d9a9e5-2ede-4445-93d7-aba6698cd9cd"
],
"role_ids" : [
"d466d7a9-5152-47e7-ba5c-1c9bb8d0ad6c",
"eeeee"
]
}
分组求和
对service集合,以"app.id","sys.id"分组,求和,统计每个"app.id","sys.id"下所有的数目,然后排序生序,并且最终对结果集进行格式化取别名处理,
$group操作符的_id为固定格式,下面包裹的是需要分组的字段;count是自定义的统计展示字段,下面包含统计方式
> db.service.aggregate([
... {
... "$group":{
... "_id":{
... "appName":"$app._id",
... "sysName":"$sys._id"
... },
... "count":{
... "$sum":1
... }
... }
... },
... {
... "$sort":{"count":-1}
... },
... {"$project":{"userCount":"$count", "sysName":"$_id.sysName", "appName":"$_id.appName", "_id":0}}
... ])
{ "userCount" : 137, "sysName" : "defaultsystem", "appName" : "amp" }
{ "userCount" : 60, "sysName" : "defaultsystem", "appName" : "acc" }
{ "userCount" : 53, "sysName" : "defaultsystem", "appName" : "rbac" }
{ "userCount" : 46, "sysName" : "defaultsystem", "appName" : "op" }
{ "userCount" : 25, "sysName" : "defaultsystem", "appName" : "vmp" }
{ "userCount" : 20, "sysName" : "defaultsystem", "appName" : "sc" }
{ "userCount" : 20, "sysName" : "defaultsystem", "appName" : "omp" }
{ "userCount" : 18, "sysName" : "defaultsystem", "appName" : "upf" }
{ "userCount" : 13, "sysName" : "defaultsystem", "appName" : "atm" }
{ "userCount" : 13, "sysName" : "defaultsystem", "appName" : "asm" }
{ "userCount" : 10, "sysName" : "defaultsystem", "appName" : "dt" }
{ "userCount" : 5, "sysName" : "defaultsystem", "appName" : "aud" }
{ "userCount" : 4, "sysName" : "defaultsystem", "appName" : "dbs" }
{ "userCount" : 3, "sysName" : "defaultsystem", "appName" : "ikejcwang" }
{ "userCount" : 2, "sysName" : "defaultsystem", "appName" : "network" }
{ "userCount" : 2, "sysName" : "defaultsystem", "appName" : "js_server_demo" }
{ "userCount" : 1, "sysName" : "defaultsystem", "appName" : "java_server_demo" }
分组求最值,平均值
以app.id分组,统计每个app._id下最大的status,并且排序降序,并且最终对结果集进行格式化取别名处理,
同理:$min(最小值),$avg(平均值),
> db.service.aggregate([
... {
... "$group":{
... "_id":{
... "appName":"$app._id"
... },
... "count":{
... "$max":"$status"
... }
... }
... },
... {
... "$sort":{"count":-1}
... },
... {"$project":{"_id":0, "appName":"$_id.appName", "maxStatus":"$count"}}
... ])
{ "appName" : "dt", "maxStatus" : 2 }
{ "appName" : "java_server_demo", "maxStatus" : 2 }
{ "appName" : "dbs", "maxStatus" : 2 }
{ "appName" : "vmp", "maxStatus" : 2 }
{ "appName" : "aud", "maxStatus" : 2 }
{ "appName" : "asm", "maxStatus" : 2 }
{ "appName" : "op", "maxStatus" : 2 }
{ "appName" : "omp", "maxStatus" : 2 }
{ "appName" : "network", "maxStatus" : 2 }
{ "appName" : "amp", "maxStatus" : 2 }
{ "appName" : "acc", "maxStatus" : 2 }
{ "appName" : "js_server_demo", "maxStatus" : 2 }
{ "appName" : "rbac", "maxStatus" : 2 }
{ "appName" : "sc", "maxStatus" : 2 }
{ "appName" : "upf", "maxStatus" : 2 }
{ "appName" : "ikejcwang", "maxStatus" : 2 }
{ "appName" : "atm", "maxStatus" : 2 }
1.3.7、其他
1、ObjectId查询
db.service.find({"svcObjectID":ObjectId("62c3e52e79a2a529fb51b80d")})
db.service.find({"svcObjectID":{"$in":[ObjectId("62c3e52e79a2a529fb51b80d"), ObjectId("62c3e52e79a2a52hehehehehehehe"),ObjectId("62c3e52e79a2a529hahahahaha")]})
2、字段内容为空查询(不是字段不存在),除了$exists,支持直接传null
db.service.find({"svcObjectID":null})
1.4、索引
查看索引
1、查看当前索引:db.colName.getIndexes()
;
2、查询当前索引的大小:db.colName.totalIndexSize()
;
> db.acs_action.getIndexes()
[
{
"v" : 2,
"unique" : true,
"key" : {
"appid" : 1,
"code" : 1
},
"name" : "appid_1_code_1"
}
]
> db.acs_action.totalIndexSize()
110592
删除索引
1、删除所有索引:db.colName.dropIndexes()
;
2、删除指定的索引,指定name:db.colName.dropIndex("indexName")
创建索引
db.colName.createIndex(keys, options)
;
keys:需要创建的索引字段,1升序创建,-1降序创建,包含组合索引;
单一索引:
db.colName.createIndex({"id": 1})
复合索引:
db.colName.createIndex({"id": 1, "createTime": -1})
options:可选参数配置,非必填,可选参数列表如下:
Parameter Type Description background
Boolean 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 unique
Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. name
string 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 sparse Boolean 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. expireAfterSeconds
integer 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 v
index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 weights document 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 default_language string 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 language_override string 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.
> db.user.createIndex({"name":1, "age": -1}, {unique: true})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
2、应用篇
2.1、mongo-driver
mongoDB官方提供了golang语言开发的驱动包:mongo-driver
,但是用起来比较繁琐,操作上有代码叠加场景,这里简单的对其二次封装处理,使单表的增删改查,批量处理,分页……多表的聚合查询,用起来更简单。
2.2、封装思路
- 抽出一个操作数据库的接口,用来做抽象处理;
- mongo的每一个库的每一张集合,都会对应一个golang的结构体对象,来做唯一映射,即满足单表所有场景下的增删改查操作;
- 提供初始化函数和创建集合对象的函数,用于项目启动时的加载项;
2.3、实现方式
1、提供一个BaseCollection接口;
package collection
import "context"
//
// BaseCollection
// @Description: 定义操作的接口
//
type BaseCollection interface {
//
// SelectPage
// @Description: 分页查询
// @param ctx
// @param filter
// @param sort
// @param skip
// @param limit
// @return int64
// @return []interface{}
// @return error
// @author: ikejcwang
// @create: 2022-10-04 16:21:08
SelectPage(ctx context.Context, filter interface{}, sort interface{}, skip, limit int64) (int64, []interface{}, error)
//
// SelectList
// @Description: 查询列表
// @param ctx
// @param filter
// @param sort
// @return []interface{}
// @return error
// @author: ikejcwang
// @create: 2022-10-04 16:21:51
SelectList(ctx context.Context, filter interface{}, sort interface{}) ([]interface{}, error)
//
// SelectOne
// @Description: 查询单条
// @param ctx
// @param filter
// @return interface{}
// @return error
// @author: ikejcwang
// @create: 2022-10-04 16:21:56
SelectOne(ctx context.Context, filter interface{}) (interface{}, error)
//
// SelectCount
// @Description: 查询统计
// @param ctx
// @param filter
// @return int64
// @return error
// @author: ikejcwang
// @create: 2022-10-04 14:36:45
SelectCount(ctx context.Context, filter interface{}) (int64, error)
//
// UpdateOne
// @Description: 更新单条
// @param ctx
// @param filter
// @param update
// @return int64
// @return error
// @author: ikejcwang
// @create: 2022-10-04 14:37:58
UpdateOne(ctx context.Context, filter, update interface{}) (int64, error)
//
// UpdateMany
// @Description: 更新多条
// @param ctx
// @param filter
// @param update
// @return int64
// @return error
// @author: ikejcwang
// @create: 2022-10-04 14:38:26
UpdateMany(ctx context.Context, filter, update interface{}) (int64, error)
//
// Delete
// @Description: 根据条件删除
// @param ctx
// @param filter
// @return int64
// @return error
// @author: ikejcwang
// @create: 2022-10-04 14:38:53
Delete(ctx context.Context, filter interface{}) (int64, error)
//
// InsetOne
// @Description: 插入单条
// @param ctx
// @param model
// @return interface{}
// @return error
// @author: ikejcwang
// @create: 2022-10-04 14:39:38
InsetOne(ctx context.Context, model interface{}) (interface{}, error)
//
// InsertMany
// @Description: 插入多条
// @param ctx
// @param models
// @return []interface{}
// @return error
// @author: ikejcwang
// @create: 2022-10-04 14:40:25
InsertMany(ctx context.Context, models []interface{}) ([]interface{}, error)
//
// Aggregate
// @Description: 聚合查询
// @param ctx
// @param pipeline
// @param result
// @return error
// @author: ikejcwang
// @create: 2022-10-04 14:46:54
Aggregate(ctx context.Context, pipeline interface{}, result interface{}) error
//
// CreateIndexes
// @Description: 创建索引,用于初始化时调用
// @param ctx
// @param indexes
// @return error
// @author: ikejcwang
// @create: 2022-10-06 13:23:20
CreateIndexes(ctx context.Context, indexes []mongo.IndexModel) error
//
// GetCollection
// @Description: 获取当前的*mongo.Collection对象
// @return *mongo.Collection
// @author: ikejcwang
// @create: 2022-10-04 17:04:45
GetCollection() *mongo.Collection
}
2、创建BaseCollection的结构体,且实现上述接口;
package collection
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
//
// BaseCollectionImpl
// @Description: collection 的实现
//
type BaseCollectionImpl struct {
DbName string
ColName string
DataBase *mongo.Database
Collection *mongo.Collection
}
func (b *BaseCollectionImpl) SelectPage(ctx context.Context, filter interface{}, sort interface{}, skip, limit int64) (int64, []interface{}, error) {
var err error
resultCount, err := b.Collection.CountDocuments(ctx, filter)
if err != nil {
return 0, nil, err
}
opts := options.Find().SetSort(sort).SetSkip(skip).SetLimit(limit)
finder, err := b.Collection.Find(ctx, filter, opts)
if err != nil {
return resultCount, nil, err
}
result := make([]interface{}, 0)
if err := finder.All(ctx, &result); err != nil {
return resultCount, nil, err
}
return resultCount, result, nil
}
func (b *BaseCollectionImpl) SelectList(ctx context.Context, filter interface{}, sort interface{}) ([]interface{}, error) {
var err error
opts := options.Find().SetSort(sort)
finder, err := b.Collection.Find(ctx, filter, opts)
if err != nil {
return nil, err
}
result := make([]interface{}, 0)
if err := finder.All(ctx, &result); err != nil {
return nil, err
}
return result, err
}
func (b *BaseCollectionImpl) SelectOne(ctx context.Context, filter interface{}) (interface{}, error) {
result := new(interface{})
err := b.Collection.FindOne(ctx, filter, options.FindOne()).Decode(result)
if err != nil {
return nil, err
}
return result, nil
}
func (b *BaseCollectionImpl) SelectCount(ctx context.Context, filter interface{}) (int64, error) {
return b.Collection.CountDocuments(ctx, filter)
}
func (b *BaseCollectionImpl) UpdateOne(ctx context.Context, filter, update interface{}) (int64, error) {
result, err := b.Collection.UpdateOne(ctx, filter, update, options.Update())
if err != nil {
return 0, err
}
if result.MatchedCount == 0 {
return 0, fmt.Errorf("Update result: %s ", "document not found")
}
return result.MatchedCount, nil
}
func (b *BaseCollectionImpl) UpdateMany(ctx context.Context, filter, update interface{}) (int64, error) {
result, err := b.Collection.UpdateMany(ctx, filter, update, options.Update())
if err != nil {
return 0, err
}
if result.MatchedCount == 0 {
return 0, fmt.Errorf("Update result: %s ", "document not found")
}
return result.MatchedCount, nil
}
func (b *BaseCollectionImpl) Delete(ctx context.Context, filter interface{}) (int64, error) {
result, err := b.Collection.DeleteMany(ctx, filter, options.Delete())
if err != nil {
return 0, err
}
if result.DeletedCount == 0 {
return 0, fmt.Errorf("DeleteOne result: %s ", "document not found")
}
return result.DeletedCount, nil
}
func (b *BaseCollectionImpl) InsetOne(ctx context.Context, model interface{}) (interface{}, error) {
result, err := b.Collection.InsertOne(ctx, model, options.InsertOne())
if err != nil {
return nil, err
}
return result.InsertedID, err
}
func (b *BaseCollectionImpl) InsertMany(ctx context.Context, models []interface{}) ([]interface{}, error) {
result, err := b.Collection.InsertMany(ctx, models, options.InsertMany())
if err != nil {
return nil, err
}
return result.InsertedIDs, err
}
func (b *BaseCollectionImpl) Aggregate(ctx context.Context, pipeline interface{}, result interface{}) error {
finder, err := b.Collection.Aggregate(ctx, pipeline, options.Aggregate())
if err != nil {
return err
}
if err := finder.All(ctx, &result); err != nil {
return err
}
return nil
}
func (b *BaseCollectionImpl) CreateIndexes(ctx context.Context, indexes []mongo.IndexModel) error {
_, err := b.Collection.Indexes().CreateMany(ctx, indexes, options.CreateIndexes())
return err
}
func (b *BaseCollectionImpl) GetCollection() *mongo.Collection {
return b.Collection
}
3、提供初始化函数,创建collection对象函数;
package collection
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"time"
)
var (
MongoClient *mongo.Client
)
const (
defaultTimeout = 50 * time.Second
maxPoolSize = 10
)
//
// InitMongo
// @Description: 初始化mongo
// @param mongoUrl
// @return error
// @author: ikejcwang
// @create: 2022-10-04 15:27:55
func InitMongo(mongoUrl string) error {
var err error
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
MongoClient, err = mongo.Connect(ctx, options.Client().ApplyURI(mongoUrl).SetMaxPoolSize(maxPoolSize))
if err != nil {
return err
}
if err := MongoClient.Ping(ctx, readpref.Primary()); err != nil {
return err
}
return nil
}
//
// CreateMongoCollection
// @Description: 创建mongo集合的服务
// @param dbName
// @param colName
// @return BaseCollection
// @return error
// @author: ikejcwang
// @create: 2022-10-04 15:15:14
func CreateMongoCollection(dbName, colName string) BaseCollection {
dataBase := MongoClient.Database(dbName)
return &BaseCollectionImpl{
DbName: dbName,
ColName: colName,
DataBase: dataBase,
Collection: dataBase.Collection(colName),
}
}
2.4、场景篇
为了演示简单处理,其实mongoDB的配置应该用.conf文件的方式引入,且可以丰富选项;
1、假设目前有一个库,一张表,那就创建一个baseCollection对象,有多个就创建多个,放在程序初始化的入口那里;
2、一个baseCollection对象,包含着该集合的绝大多数数据操作(丰富的增删改查);
3、同时也提供初始化索引的API,因为mongoDB的数据库,集合无需主动创建,引用到了,就是创建了。故把索引结构初始化的逻辑也放到应用程序这一侧
2、如果有特殊的业务处理baseCollection里面封装的函数没有覆盖到,需要原始API,例如初始化时创建各种类型的索引,它也提供着原始的*mongo.Collection的属性,可以直接调用它来操作,类似于:appCollection.GetCollection()
;
var (
appCollection *mongo.Collection
userCollection *mongo.Collection
// ……多个
)
//
// Init
// @Description: 初始化mongo
// @param mongoUrl
// @author: ikejcwang
// @create: 2022-10-06 13:40:05
func Init(mongoUrl string) error {
if err := collection.InitMongo(mongoUrl); err != nil {
panic(err)
}
ctx := context.TODO()
// 逐一初始化集合对象
userCollection := collection.CreateMongoCollection("test", "user")
appCollection := collection.CreateMongoCollection("test", "user")
// 依次给集合初始化索引
indexes := []mongo.IndexModel{
{
Keys: bson.D{{"name", 1}, {"age", -1}},
Options: options.Index().SetUnique(true),
},
}
if err := userCollection.CreateIndexes(ctx, indexes); err != nil {
fmt.Printf("init indexes error: %v", err.Error())
return err
}
// ………………多个
return nil
}
2.5、测试
1、创建一个集合的结构体,属性映射到所有集合字段上,同时提供bson格式化转换的函数;
//
// UserDto
// @Description: app集合的字段映射
//
type UserDto struct {
Name string `json:"name" bson:"name"`
Age int64 `json:"age" bson:"age"`
}
func (a *UserDto) BsonByte(item interface{}) {
bytes, err := bson.Marshal(item)
if err != nil {
panic(err)
}
bson.Unmarshal(bytes, a)
}
2、测试,初始化Mongo客户端连接,创建bsonCollection对象,写查询条件,调用对应函数,格式化结果集,输出结果集;
func main() {
if err := collection.InitMongo("mongodb://user:password@9.13.28.88:27017/?authSource=admin"); err != nil {
panic(err)
}
ctx := context.TODO()
// 初始化user的集合对象
userCollection := collection.CreateMongoCollection("test", "user")
// 初始化索引
indexes := []mongo.IndexModel{
{
Keys: bson.D{{"name", 1}, {"age", -1}},
Options: options.Index().SetUnique(true),
},
}
if err := userCollection.CreateIndexes(ctx, indexes); err != nil {
fmt.Printf("init indexes error: %v", err.Error())
}
filter := bson.M{
"name": bson.M{
"$regex": "ike",
"$options": "i",
},
}
list, err := userCollection.SelectList(ctx, filter, nil)
if err != nil {
panic(err)
}
userList := make([]*UserDto, 0)
for _, item := range list {
app := new(UserDto)
app.BsonByte(item)
userList = append(userList, app)
}
fmt.Printf("userList: length:%v\n", len(userList))
bytes, _ := json.Marshal(userList)
fmt.Printf("userList: data:%v\n", string(bytes))
}
3、执行:go run xxx.go
wangjinchao@IKEJCWANG-MB0 14_ike_goMongo % go run test/test.go
userList: length:6
userList: data:[{"name":"ikejcwang","age":35},{"name":"ikejcwang","age":34},{"name":"ikejcwang","age":33},{"name":"ikejcwang","age":32},{"name":"ikejcwang","age":31},{"name":"ikejcwang","age":0}]
2.6、应用
类似java web框架开发那种分层处理,这里封装的是orm层(dao层),直接对接数据库,前面应该还有controller,service……
3、自动化部署篇
未完待续……