一种文档数据库,由C++编写,数据存储结构为BSON
数据类型 | 描述 |
---|---|
String | 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。 |
Integer | 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。 |
Boolean | 布尔值。用于存储布尔值(真/假)。 |
Double | 双精度浮点值。用于存储浮点值。 |
Min/Max keys | 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。 |
Arrays | 用于将数组或列表或多个值存储为一个键。 |
Timestamp | 时间戳。记录文档修改或添加的具体时间。 |
Object | 用于内嵌文档。 |
Null | 用于创建空值。 |
Symbol | 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。 |
Date | 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。 |
Object ID | 对象 ID。用于创建文档的 ID。 |
Binary Data | 二进制数据。用于存储二进制数据。 |
Code | 代码类型。用于在文档中存储 JavaScript 代码。 |
Regular expression | 正则表达式类型。用于存储正则表达式。 |
结构化数据库 | NOSQL数据库 | 名称 |
---|---|---|
database | database | 数据库 |
row | collection | 数据库表/集合 |
column | field | 字段/域 |
index | index | 索引 |
joins | / | 表连接/mongodb不支持(3.2后可以通过聚合) |
primary key | primary key | 主键/mongodb自动将_id设为主键 |
## 在windows下
## 启动服务:管理员命令下
mongod --dbpath D:mongoDB\data\db
## Linux下
## 进入bin目录,启动服务
./mongod --config /usr/local/mongodb/etc/mongodb.conf
## 进入控制台
mongo
help #:查看帮助指令
建库规范
show dbs # 展示所有的数据库
use tablename # 切换到某个库
db # 查看当前库
db.dropDatabase() # 删除当前库
一般不用特意去创建集合,在insert时指定就可以。
集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。
建集合规范
db.createCollection(name, options)
# 参数options:
#capped Boolean true:固定集合,当到达最大值会覆盖最早的文档。(为true时,size也要指定)
#size: 字节数
#max: 包含文档的最大数量
# 举例
db.createCollection('collection')
db.createCollection("coll", { capped: true, autoIndexId : true, size: 6142800, max : 10000})
# 创建固定集合
# 它的自然顺序保证与文档插入顺序一致。
# 大小固定,一旦超过大小,最老的数据将被删除,新数据将被添加到末端。(栈)
# 可用于日志数据 或 归档数据
db.createCollection("coll_new",{capped: true, size: 20480, max: 100})
# 使用stats()检查集合大小
db.coll_new.stats()
# 自然排序
db.coll.find().sort({$natural:-1}).limit(10)
show collections
db.collection.drop()
所有存储在集合中的数据都是以BSON结构存储的。
BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。
# 单条插入; document 为一份文档数据,可以先定义
document = ({'title':'标题','name':'xzy'});
db.COLLECTION_NAME.insert(document)
~~db.COLLECTION_NAME.save(document)~~
db.COLLECTION_NAME.insertOne(document)
db.COLLECTION_NAME.replaceOne(document)
# 多条插入
db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>, # 写入策略 默认为1 要求写入确认;0:不要求写入确认;-1:忽略网络错误;2:要求以写入到副本集的主服务器和一个备用服务器;majority:要求已写入到副本集中的大多数服务器中。
ordered: <boolean> # 是否按顺序写入 默认是true
}
)
注意:save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne() 或 db.collection.replaceOne() 来代替。
insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
db.coll.find()[.pretty()] # 查看所有数据,.pretty()为了数据格式化
db.coll.findOne() # 获取一条
db.coll.find().count() # 获取数量
# 相当于 where name = 'xzy'
db.coll.find({'name':'xzy'})
db.coll.find({"_id":ObjectId("5fb23fe3c33b909f22d5a31e")})
# 找出所有存在name字段的数据 并返回name属性
db.coll.find({'name':{$exists:1}},{name:1})
# AND操作 where name = 'xzy' and age = 23
db.coll.find({'name':'xzy','age':23});
# OR操作 where name='xzy' or age>=25
# OR操作 必须先满足第一个条件,在满足第二个条件,所有子句都支持使用自己的索引
db.coll.find(
{$or:[
{'name':'xzy'},
{'age':{$gte:25}}
]
}
)
# 模糊查询 name 包含 xzy
db.coll.find({name:/xzy/})
# 已 jay开头
db.coll.find({name:/^jay})
# 已 jay结尾
db.coll.find({name:/jay$/})
# where name = 'xzy' and age < 20
db.coll.find({'name':'xzy','age':{$lt:20}});
# where title='MongoDB' AND (age>=20 or url='www.xx.com')
db.mycol.find({'title':'MongoDB',$or:[{'age':{$gte:20}},{'url':'www.xx.com'}]})
# where name in ('xzy','Jay')
db.coll.find({'name':{$in:['xzy','Jay']}})
# where name not in ('xzy','Jay')
db.coll.find({'name':{$nin:['xzy','Jay']}})
# 只有当 favs 里同时包含['play','b-ball']才会被查询出来
db.coll.find({'favs':{$all:['play','b-ball']}})
# slice 需要数组
# 接收两个参数
# 如果只有一个参数,则为数据项总和
# 如果有两个参数,那么第一个参数定义偏移,第二个参数定义限制
# 第一个参数可以为 负数 代表从后往前
db.coll.find({'name':'xzy','favs':{$slice:[0,2]}})
简单的字段去重:
db.coll.distinct('name')
# 输出结果为:
['xzy','jay','zqy']
分组查询
db.coll.group({
key: {name:true}, # 指定希望用哪个键对结果分组
initial: {Total: 0}, # 元素开始统计的起始基数
reduce: function(items,prev){prev.Total += 1} # 分组操作
})
# 还有另外三个参数可选
# keyf: 如果不希望使用文档中已有的键对结果进行分组,可以使用该参数替代
# cond:添加额外的语句条件
# finalize:指定一个函数,用于最终结果返回之前。
案例:
# 使用idCard 和 idName 进行分组查询
db.coll.group({
key: {idCard:true,idName:true},
initial:{count:0},
reduce:function(items,prev){
prev.count += 1;
}
});
结果:
{
"idCard" : "990101197901180002",
"idName" : "Jay",
"count" : 1
}
...
limit、skip
# 取前5条
db.coll.find().limit(5)
# 跳过5条
db.coll.find().skip(5)
# 结合以上两个函数就可以实现分页。
db.coll.find().skip(0).limit(5) # 第一页
db.coll.find().skip(5).limit(5) # 第二页
# 根据id排序 1为升序 -1为降序
db.coll.find().sort({_id:1})
# 年龄大于20 且根据年龄倒序
db.coll.find({'age':{$gt:20}}).sort({age:-1})
## 自然顺序 根据插入库的顺序排序
db.coll.find().sort({$natural:-1}).limit(10)
MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。
# $project : 可以控制字段的输出顺序 以及是否输出
db.coll.aggregate({
$project:{
_id : 0,
idName:1,
idCard:1,
phone:1,
favs:1
}
})
可以看个案例:
# $match 要在 $project 之前
db.coll.aggregate(
{
$match:{
age:{$gt:20}
}
},
{
$project:{
_id : 0,
idName:1,
idCard:1,
phone:1,
favs:1
}
},
{
$skip:0
},
{
$limit:2
},
{
$sort:{
phone:1
}
}
)
相当于
select idName,idCard,phone,favs from coll where age>20 limit 0,2 order by phone asc
分组
# 案例一:
db.coll.aggregate([
{$group:{
_id:{
# groupName 分组名 可自定义
groupName:'$idCard'
},
total:{
# 计数器
$sum:1
}
}}
])
db.coll.aggregate([{$group:{_id:{groupName:'$idCard'},total:{$sum:1}}}])
# 相当于
select idCard as groupName,count(1) as total where coll group by idCard
# 案例二:
db.coll.aggregate([
{$match:{
idName:{$ne:''}
}},
{
$group:{
# _id 不可改为别的
_id: "$idName",
ageTotal:{$sum:'$age'}
}
},
{$match:{ageTotal : {$gte:20}}}
])
相当于
select idName as _id,count(age) as ageTotal from coll where idName <> '' group by idName having ageTotal >= 20
$group
是固定的,表示这里一个分组聚合操作。_id
表示需要根据哪一些列进行聚合,其实一个JSON对象,其key/value对分别表示结果列的别名以及需要聚合的的数据库列。ageTotal
表示聚合列的列名。$sum
表示要进行的聚合操作,后面的1表示每次加1。表达式 | 描述 | 实例 |
---|---|---|
$sum | 计算总和 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",num_tutorial:{ s u m : " sum:" sum:"price"}}]) |
$avg | 计算平均值 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",num_tutorial:{ a v g : " avg:" avg:"price"}}]) |
$min | 获取最小值 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",num_tutorial:{ m i n : " min:" min:"price"}}]) |
$max | 获取最大值 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",num_tutorial:{ m a x : " max:" max:"price"}}]) |
$push | 在结果文档中插入一个值到一个数组中 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",num_tutorial:{ p u s h : " push:" push:"price"}}]) |
$addToSet | 在结果文档中插入一个值到一个数组中,但不创建副本 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",num_tutorial:{ a d d T o S e t : " addToSet:" addToSet:"price"}}]) |
$first | 根据资源文档排序获取第一个文档的数据 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",price_first:{ f i r s t : " first:" first:"price"}}]) |
$last | 根据资源文档的排序获取最后一个文档 | db.coll.aggregate([KaTeX parse error: Expected '}', got 'EOF' at end of input: group:{_id:"price",price_last:{ l a s t : " last:" last:"price"}}]) |
# 获取最大最小的年龄
db.coll.aggregate([
{
"$group":{
_id:1,
maxAge: {"$max":"$age"},
minAge: {"$min":"$age"}
}
}
])
# 时间处理 格式化
db.coll.aggregate([
{
$project:{
"date":{
"$dateToString":{
"format":"%Y-%m-%d %H:%M:%S",
"date":"$date"
}
},"idName":1,"idCard":1,"age":1
}
},
{
$match:{"date":{$ne:null}}
}
])
SQL | NOSQL |
---|---|
where | $match |
group by | $group |
having | $match |
select | $project |
order by | $sort |
limit | $limit |
sum() | $sum |
count() | $sum |
join | $lookup(v3.2新增) |
length() | $size |
isNull() | $ifNull |
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
# 危险的操作写法
# 将name为zy的第一条数据 覆盖为 age:25
db.coll.update({name:'zy'},{'age':25})
# 将age为444的一条数据覆盖为 ...
db.coll.update({'age':444},
{
'idName':"Kobe",
'idCard':'990101197901180008',
'age':44,
'favs':['magic','b-ball','sing','play'],
'phone':'11011000001',
'star':true
})
# 修改 idName=xiaolili 满足 age=15 的一条数据
db.coll.update({age:15},{$set:{idName:"xiaolili"}})
# 修改 idName=xiaolili 满足 age=15 的所有数据
# 总共4个参数,第四个参数表示有多条数据符合筛选条件的话是否全部更改,默认为0(false)只改第一条,改为1(true)后表示全部更改 ;第3个参数1(true)表示如果没有符合条件的记录是否新增一条记录,1(true)表示新增,0(false)表示不新增
db.coll.update({age:15},{$set:{name:"xiaolili"}},1,1)
# set age=age-1
db.coll.update({'name':'xzy'},{$inc:{age:-1}},false,1)
# set age=age+1 where age>10
db.coll.update( { "age" : { $gt : 10 } } , { $inc : { "age" : 1} },false,false )
db.collection.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
# 删除所有
db.coll.remove({})
# 条件删除
db.coll.remove({'name':'xzy','age':24})
每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。
由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。
如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
索引不能被以下的查询使用:
# 创建索引
db.collection.createIndex(keys,options)
# 获取所有索引
db.collection.getIndexes()
# 删除所有的索引
db.colletion.dropIndexes()
# 删除指定索引
db.collection.dropIndex("索引名称")
# 复合索引,唯一,后台创建
db.coll.createIndex({"idName":1,"age":-1},{unique:true,background: true})
# 180s 后自动删除
db.coll.createIndex({"createDate": 1},{expireAfterSeconds: 180,background:true})
# 使用explain 检验是否使用了索引
db.mongoTest.find({"idName":"Jay"}).explain();
option参数表如下:
参数 | Type | 描述 |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为false。 |
unique | Boolean | 是否唯一。默认false |
name | string | 索引名称,如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | 索引号版本 | |
weights | document | 权重,1~99999之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
实体类注解
注解 | 说明 |
---|---|
@Document | 标识当前实体是一个mongodb实体 @Document(collection = “myColl”) 也可以用这个标识集合 |
@Id | 标识主键 只能存在一个 |
@Indexed | 标识索引 |
@CompoundIndex | 标识联合索引 |
@Field | 对当前字段的额外内容进行定义,主要是用来定义集合中字段实际名称 |
@Transient | 标识此字段为java属性而非mongodb字段 |
@DBRef | 将此字段同另外一个mongodb的文档进行关联 |
@Data
// 指定某个collection
@Document(collection = "mongoTest")
public class UserBo implements Serializable {
@Id
private String id;
private String idCard;
private String idName;
private String phone;
private Integer age;
private String[] favs;
@Transient
private String note;
private Date date;
private String dateString;
}
@Autowired
private MongoTemplate mongoTemplate;
@RequestMapping("/insertOne")
public String insertOne(@RequestBody UserBo bo){
if(bo==null){
return "fail";
}
bo.setDate(new Date());
// 使用save 方法,当主键冲突时,会覆盖
//mongoTemplate.save(bo);
// 使用insert 方法 推荐使用insert方法,当主键冲突时,会报错
mongoTemplate.insert(bo);
return "succ";
}
@RequestMapping("/delOne")
public String delOne(@RequestBody UserBo bo){
Query query = new Query(Criteria.where("id").is(bo.getId()));
// 查找并删除
// mongoTemplate.findAllAndRemove(query,UserBo.class);
DeleteResult res = mongoTemplate.remove(query, UserBo.class);
System.out.println("删除了:"+res.getDeletedCount()+"条");
return "succ";
}
@RequestMapping("/updateOne")
public String updateOne(@RequestBody UserBo bo){
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(bo.getId()));
Update up = new Update();
if(bo.getIdName()!=null){
up.set("idName",bo.getIdName());
}
// 修改单条
UpdateResult res = mongoTemplate.upsert(query,up,UserBo.class);
// mongoTemplate.updateFirst(query,up,UserBo.class);
// 批量更新
// mongoTemplate.updateMulti(query,up,UserBo.class);
System.out.println("修改了:"+res.getModifiedCount()+"条");
return "suc";
}
@RequestMapping("/queryInfo")
public String queryInfo(@RequestBody UserBo bo){
Query query = new Query();
// 全量查询
List<UserBo> list = mongoTemplate.findAll(UserBo.class);
// 去重获取字段 获取不同的idName
List<UserBo> disList = mongoTemplate.findDistinct(query,"idName", UserBo.class,UserBo.class);
}
@RequestMapping("/queryInfo")
public String queryInfo(@RequestBody UserBo bo){
Query query1 = new Query();
// where age <> '' and age is not null
// 注意 .ne("") 要放在 .ne(null) 前面
List<UserBo> testList = mongoTemplate.find(query1.addCriteria(Criteria.where("age").ne("").ne(null)),UserBo.class);
Query query2 = new Query();
// in 可传多个对象 也可传集合
List<UserBo> testList2 = mongoTemplate.find(query2.addCriteria(Criteria.where("age").ne("").ne(null))
.addCriteria(Criteria.where("favs").in("kill","work")),UserBo.class);
// OR查询
Query orQuery = new Query();
// where age <> '' and age is not null and age>200 and idName like '%魈%' and (id = 'NO007' OR id = 'NO008')
List<UserBo> userList = mongoTemplate.find(orQuery.addCriteria(Criteria.where("age").ne("").ne(null).gt(200))
// 模糊查询
.addCriteria(Criteria.where("idName").regex(".*"+"魈"+".*"))
// OR 操作
.addCriteria(new Criteria().orOperator(Criteria.where("id").is("NO007"),Criteria.where("id").is("NO008")))
,UserBo.class);
// 排序 分页 查询
Query query = new Query();
// where age <> '' and age is not null order by age asc limit 5,5
List<UserBo> sortList = mongoTemplate.find(
query.with(Sort.by(Sort.Order.asc("age"))).addCriteria(Criteria.where("age").ne("").ne(null)).limit(5).skip(5),
UserBo.class);
}
@RequestMapping("/queryInfo")
public String queryInfo(@RequestBody UserBo bo){
// 基本的聚合查询
Aggregation aggregation = Aggregation.newAggregation(
// project 需要展示的字段
Aggregation.project("idName","idCard","age"),
// 条件查询
Aggregation.match(Criteria.where("age").ne("").ne(null).gte(20)),
// 分组 as:别名
Aggregation.group("idName").count().as("count"),
// 排序
Aggregation.sort(Sort.by(Sort.Order.desc("idName")))
);
AggregationResults<UserBo> results = mongoTemplate.aggregate(aggregation, UserBo.class, UserBo.class);
List<UserBo> aggList = results.getMappedResults();
// 日期处理 格式化
Aggregation agg = Aggregation.newAggregation(Aggregation.match(Criteria.where("date").ne(null)),
Aggregation.project("date").andExpression("{$dateToString:{format:'%Y-%m-%d %H:%M:%S',date:'$date'}}").as("dateString")
);
AggregationResults<UserBo> aggResults = mongoTemplate.aggregate(agg,UserBo.class,UserBo.class);
List<UserBo> aggregationList =aggResults.getMappedResults();
}