MongoDB入门

1 MongoDB中的基本概念及原理

1.1 MongoDB介绍

官网地址:https://www.mongodb.com/
MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。 MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。

1.2 MongoDB概念解析

不管我们学习什么数据库都应该学习其中的基础概念,在mongodb中基本的概念是文档、集合、数据库,下面我们挨个介绍。
下表将帮助您更容易理解Mongo中的一些概念:

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins -- 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

通过下图实例,我们也可以更直观的了解Mongo中的一些概念:

001.png

1.2.1 数据库

一个mongodb中可以建立多个数据库。MongoDB的默认数据库为"test",该数据库存储在data目录中(安装时,可以默认可以指定,但是该目录必须是存在的)。MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库放置在不同的文件中。
执行 "show dbs" 命令可以显示所有数据库的列表。

> show dbs 
local 0.078GB 
test 0.078GB 
>

执行 "db" 命令可以显示当前数据库对象或集合。

> db 
test 
>

执行 "use"命令,可以连接到一个指定的数据库。

> use local 
switched to db local 
> db 
local 
>

数据库命名规范
数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串。
不能是空字符串
不得含有' '(空格)、.、$、/、\和\0 (空字符)
应全部小写
最多64字节

有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

1.2.2 集合

集合就是 MongoDB 文档组,类似于 RDBMS中的表。
集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。比如,我们可以将以下不同数据结构的文档插入到集合中:

{"site":"www.baidu.com"} 
{"site":"www.google.com","name":"Google"} 

当第一个文档插入时,集合就会被创建。

一个collection(集合)中的所有field(域)是collection(集合)中所有document(文档)中包含 的field(域)的并集。

集合命名规范
集合名不能是空字符串""。
集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
集合名不能以"system."开头,这是为系统集合保留的前缀。
用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现。
如下实例:

db.col.findOne()

capped collections
Capped collections 就是固定大小的collection。它有很高的性能以及队列过期的特性(过期按照插入的顺序)。有点和 "RRD" 概念类似。Capped collections 是高性能自动的维护对象的插入顺序。它非常适合类似记录日志的功能。和标准的collection 不同,你必须要显式的创建一个capped collection,指定一个 collection 的大小,单位是字节。collection 的数据存储空间值提前分配的。
Capped collections 可以按照文档的插入顺序保存到集合中,而且这些文档在磁盘上存放位置也是按照插入顺序来保存的,所以当我们更新Capped collections 中文档的时候,更新后的文档不可以超过之前文档的大小,这样话就可以确保所有文档在磁盘上的位置一直保持不变。
由于 Capped collection 是按照文档的插入顺序而不是使用索引确定插入位置,这样的话可以提高增添数据的效率。MongoDB 的操作日志文件 oplog.rs 就是利用 Capped Collection 来实现的。
要注意的是指定的存储大小包含了数据库的头信息

db.createCollection("mycoll", {capped:true, size:100000})

在 capped collection 中,你能添加新的对象。
能进行更新,然而,对象不会增加存储空间。如果增加,更新就会失败 。
使用 Capped Collection 不能删除一个文档,可以使用 drop() 方法删除 collection 所有的行。
删除之后,你必须显式的重新创建这个 collection。
在32bit机器中,capped collection 最大存储为 1e9( 1X10的9次方)个字节。

1.2.3 文档

文档是一组键值(key-value)对(即 BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。
一个简单的文档例子如下:

{"site":"www.baidu.com", "name":"百度"}

下表列出了 RDBMS 与 MongoDB 对应的术语:

RDBMS MongoDB
数据库 数据库
表格 集合
文档
字段
表联合 嵌入文档
主键 主键 (MongoDB 提供了 key 为 _id )

需要注意的是
文档中的键/值对是有序的。
文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
MongoDB区分类型和大小写。
MongoDB的文档不能有重复的键。
文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
文档键命名规范
键不能含有\0 (空字符)。这个字符用来表示键的结尾。
.和$有特别的意义,只有在特定环境下才能使用。
以下划线"_"开头的键是保留的(不是严格要求的)。

1.2.4 元数据

数据库的信息是存储在集合中。它们使用了系统的命名空间:

dbname.system.*

在MongoDB数据库中名字空间 .system.* 是包含多种系统信息的特殊集合(Collection),如下:

集合命名空间 描述
dbname.system.namespaces 列出所有名字空间
dbname.system.indexes 列出所有索引
dbname.system.profifile 包含数据库概要(profifile)信息
dbname.system.users 列出所有可访问数据库的用户
dbname.system.sources 包含复制对端(slave)的服务器信息和状态

对于修改系统集合中的对象有如下限制:
在{{system.indexes}}插入数据,可以创建索引。但除此之外该表信息是不可变的(特殊的drop index命令将自动更新相关信息)。
{{system.users}}是可修改的。
{{system.profile}}是可删除的。

1.3 MongoDB 数据类型

下表为MongoDB中常用的几种数据类型:

数据类型 说明 解释 举例
String 字符串 UTF-8 编码的字符串才是合法的。 {“v”:“baidu”}
Integer 整型数值 根据你所采用的服务器,可分为32 位或 64 位。 {“v”:1}
Boolean 布尔值 用于存储布尔值(真/假)。 {“v”:true}
Double 双精度浮点值 用于存储浮点值 {“v”:3.14}
ObjectID 对象ID 用于创建文档的ID {“_id”:ObjectId(“123123”)}
Array 数组 用于将数组或列表或多个值存储为一个键 {“arr”:[“a”,“b”]}
Timestamp 时间戳 从开始纪元开始的毫秒数
Object 内嵌文档 文档可以作为文档中某个key的value {“o”:{“foo”:“bar”}}
Null 空值 表示空值或者未定义的对象 {“v”:null}
Date 日期 日期时间,用Unix日期格式来存储当前日期或时间。 {“date”:new Date()}
Regular 正则表达式 文档中可以包含正则表达式,遵循JS语法 {“v”:/bd/i}
Code 代码 可以包含JS代码 {“x”:function(){}}
File 文件 1、二进制转码(Base64)后存储(<16M) 2、GridFS(>16M) GridFS 用两个集合来存储一个文件:fs.files与fs.chunks

下面说明下几种重要的数据类型。

1.3.1 ObjectId

ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是:
前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时
接下来的 3 个字节是机器标识码
紧接的两个字节由进程 id 组成 PID
最后三个字节是随机数

002.png

MongoDB 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象。由于 ObjectId 中保存了创建的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过getTimestamp 函数来获取文档的创建时间:

> var newObject = ObjectId() 
> newObject.getTimestamp() 
ISODate("2017-11-25T07:21:10Z")

ObjectId 转为字符串

> newObject.str 
5a1919e63df83ce79df8b38f

1.3.2 字符串

BSON 字符串都是 UTF-8 编码。

1.3.3 时间戳

BSON有一个特殊的时间戳类型用于MongoDB内部使用,与普通的日期类型不相关。时间戳值是一个 64 位的值。其中:前32位是一个 time_t 值(与Unix新纪元相差的秒数),后32位是在某秒中操作的一个递增的序数。
在单个 mongod 实例中,时间戳值通常是唯一的
在复制集中, oplog 有一个 ts 字段。这个字段中的值使用BSON时间戳表示了操作时间。
BSON 时间戳类型主要用于 MongoDB 内部使用。在大多数情况下的应用开发中,你可以使用BSON 日期类型。

1.3.4 日期

表示当前距离 Unix新纪元(1970年1月1日)的毫秒数。日期类型是有符号的, 负数表示 1970 年之前的日期。

> var mydate1 = new Date() //格林尼治时间 
> mydate1 ISODate("2018-03-04T14:58:51.233Z") 
> typeof mydate1 
object 
> var mydate2 = ISODate() //格林尼治时间 
> mydate2 ISODate("2018-03-04T15:00:45.479Z") 
> typeof mydate2 
object

这样创建的时间是日期类型,可以使用 JS 中的 Date 类型的方法。
返回一个时间类型的字符串:

> var mydate1str = mydate1.toString() 
> mydate1str 
Sun Mar 04 2018 14:58:51 GMT+0000 (UTC) 
> typeof mydate1str 
string

或者

> Date() 
Sun Mar 04 2018 1

2 MongoDB安装和常用命令

2.1 安装

下载地址:https://www.mongodb.com/download-center#community

下载安装包并解压 :

wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-3.6.11.tgz 
tar -zxvf mongodb-linux-x86_64-rhel70-3.6.11.tgz
mv mongodb-linux-x86_64-rhel70-3.6.11 mongodb 
#一定注意要创建数据存储目录,默认路径/data/db 
mkdir -p /data/db

配置环境变量:

vim /etc/profile
export PATH=$JAVA_HOME/bin:/root/mongodb/bin:$PATH
source /etc/profile

2.2 启动

通过配置文件方式启动

mongod --config 配置文件路径
mongod -f 配置文件路径

说明:-f是--config的缩写。配置文件,需要手动创建,参考以下配置即可。
创建mongodb.cfg配置文件

#数据库文件位置
dbpath=/root/mongodb/singleton/data 
#日志文件位置
logpath=/root/mongodb/singleton/logs/mongodb.log 
#以追加方式写入日志
logappend=true
#是否以守护进程方式运行
fork=true
#绑定客户端访问的ip 0.0.0.0 不绑定ip 
bind_ip=192.168.10.135
#默认端口号27017
port=27017

创建数据存储目录和日志文件目录:

mkdir /root/mongodb/singleton/data -p 
mkdir /root/mongodb/singleton/logs -p

通过配置文件方式启动:

mongod -f /root/mongodb/sinleton/mongodb.cfg

2.3 连接客户端

mongo 192.168.10.135:27017

常用客户端
自带命令行客户端
Navicat for mongodb
MongoVUE
Studio 3T
Robo 3T
RockMongo

2.4 常用命令

2.4.1 创建数据库

MongoDB 创建数据库的语法格式如下:

use DATABASE_NAME

如果数据库不存在,则创建数据库,否则切换到指定数据库。以下实例我们创建了数据库 zhoutian:

> use zhoutian 
switched to db zhoutian
> db 
zhoutian 
>

如果你想查看所有数据库,可以使用 show dbs 命令:

> show dbs 
admin 0.000GB 
local 0.000GB 
>

可以看到,我们刚创建的数据库zhoutian并不在数据库的列表中,要显示它,我们需要向zhoutian数据库插入一些数据。

> db.mycollection.insert({"name":"周天"}) 
WriteResult({ "nInserted" : 1 }) 
> show dbs 
local 0.078GB 
zhoutian 0.078GB 
test 0.078GB 
>

2.4.2 删除数据库

MongoDB 删除数据库的语法格式如下:

db.dropDatabase()

删除当前数据库,默认为 test,你可以使用 db 命令查看当前数据库名。以下实例我们删除了数据库 zhoutian。
首先,查看所有数据库:

> show dbs 
local 0.078GB 
zhoutian 0.078GB 
test 0.078GB

接下来我们切换到数据库 zhoutian:

> use zhoutian
switched to db zhoutian
>

执行删除命令:

> db.dropDatabase() 
{ "dropped" : "zhoutian", "ok" : 1 } 

最后,我们再通过show dbs命令数据库是否删除成功:

show dbs 
local 0.078GB 
test 0.078GB 
>

2.4.3 创建集合

MongoDB中使用 createCollection()方法来创建集合。
语法格式:

db.createCollection(name, options)

参数说明:
name: 要创建的集合名称
options: 可选参数, 指定有关内存大小及索引的选项。options 可以是如下参数:

字段 类型 描述
capped 布尔 (可选)如果为 true,则创建固定集合。
固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。
当该值为 true 时,必须指定 size 参数。
autoIndexId 布尔 (可选)如为 true,自动在 _id 字段创建索引。默认为 false。
size 数值 (可选)为固定集合指定一个最大值(以字节计)。
如果 capped 为true,也需要指定该字段。
max 数值 (可选)指定固定集合中包含文档的最大数量。

在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。
在 test 数据库中创建 mycollection集合:

> use test 
switched to db test 
> db.createCollection("mycollection") 
{ "ok" : 1 } 
>

如果要查看已有集合,可以使用 show collections或show tables 命令:

> show collections 
mycollection 
system.indexes

下面是带有几个关键参数的 createCollection() 的用法:
创建固定集合 mycol,整个集合空间大小 6142800 KB, 文档最大个数为 10000 个。

> db.createCollection("mycol", { capped : true, autoIndexId : true, size : 6142800, max : 10000 } )
{ "ok" : 1 }
>

在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。

> db.mycol2.insert({"name" : "嗨"})
> show collections
mycol2
...

2.4.4 删除集合

集合删除语法格式如下:

db.collection_name.drop()

以下实例删除了zhoutian数据库中的集合 site:

> use zhoutian 
switched to db zhoutian 
> show tables 
site 
> db.site.drop() 
true 
> show tables 
>

2.4.5 插入文档

MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:

db.COLLECTION_NAME.insert(document)
或
db.COLLECTION_NAME.save(document)

save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne() 或 db.collection.replaceOne() 来代替。
insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。
以下文档可以存储在 MongoDB 的zhoutian数据库 的 mycollection 集合中:

>db.mycollection.insert({title: 'MongoDB 教程', 
description: 'MongoDB 是一个 Nosql 数据库', 
by: '周天', 
url: 'http://www.zhoutian.com', 
tags: ['mongodb', 'database', 'NoSQL'], 
likes: 100 
})
>var document = {title: 'MongoDB 教程', 
description: 'MongoDB 是一个 Nosql 数据库', 
by: '周天', 
url: 'http://www.zhoutian.com', 
tags: ['mongodb', 'database', 'NoSQL'], 
likes: 100 
}
>db.mycollection.insert(document)

2.4.6 更新文档

MongoDB 使用 update() 和 save() 方法来更新集合中的文档。
update() 方法用于更新已存在的文档。语法格式如下:

db.COLLECTION_NAME.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   }
)

参数说明:
query : update的查询条件,类似sql update查询内where后面的。
update : update的对象和一些更新的操作符(如,inc...)等,也可以理解为sql update查询内set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,抛出异常的级别。

我们在集合 mycollection中插入如下数据:

>db.mycollection.insert({
    title: 'MongoDB 教程', 
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '周天',
    url: 'http://www.zhoutian.com',
    tags: ['mongodb', 'database', 'NoSQL'],
    likes: 100
})

接着我们通过 update() 方法来更新标题(title):

>db.mycollection.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })   # 输出信息
> db.col.find().pretty()
{
        "_id" : ObjectId("56064f89ade2f21f36b03136"),
        "title" : "MongoDB",
        "description" : "MongoDB 是一个 Nosql 数据库",
        "by" : "周天",
        "url" : "http://www.zhoutian.com",
        "tags" : [
                "mongodb",
                "database",
                "NoSQL"
        ],
        "likes" : 100
}
>

可以看到标题(title)由原来的 "MongoDB 教程" 更新为了 "MongoDB"。
以上语句只会修改第一条发现的文档,如果你要修改多条相同的文档,则需要设置 multi 参数为 true。

>db.mycollection.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}},{multi:true})

save() 方法通过传入的文档来替换已有文档,_id 主键存在就更新,不存在就插入。语法格式如下:

db.COLLECTION_NAME.save(
   <document>,
   {
     writeConcern: <document>
   }
)

参数说明:
document : 文档数据。
writeConcern :可选,抛出异常的级别。

以下实例中我们替换了 _id 为 56064f89ade2f21f36b03136 的文档数据:

>db.mycollection.save({
    "_id" : ObjectId("56064f89ade2f21f36b03136"),
    "title" : "MongoDB",
    "description" : "MongoDB 是一个 Nosql 数据库",
    "by" : "zhoutian",
    "url" : "http://www.zhoutian.com",
    "tags" : [
            "mongodb",
            "NoSQL"
    ],
    "likes" : 110
})

替换成功后,我们可以通过 find() 命令来查看替换后的数据

>db.mycollection.find().pretty()
{
        "_id" : ObjectId("56064f89ade2f21f36b03136"),
        "title" : "MongoDB",
        "description" : "MongoDB 是一个 Nosql 数据库",
        "by" : "zhoutian",
        "url" : "http://www.zhoutian.com",
        "tags" : [
                "mongodb",
                "NoSQL"
        ],
        "likes" : 110
}
> 

2.4.7 删除文档

MongoDB 使用remove()方法是用来移除集合中的数据。在执行remove()方法前先执行find()方法来判断执行的条件是否正确,这是一个比较好的习惯。
remove() 方法的基本语法格式如下所示:

db.COLLECTION_NAME.remove( <query>, <justOne> )

如果你的 MongoDB 是 2.6 版本以后的,语法格式如下:

db.COLLECTION_NAME.remove( <query>, { justOne: <boolean>, writeConcern: <document> } )

参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值false,则删除所有匹配条件的文档。
writeConcern :(可选)抛出异常的级别。

以下文档我们执行两次插入操作:

>db.mycollection.insert({
    title: 'MongoDB 教程',
    description: 'MongoDB 是一个 Nosql 数据库', 
    by: '周天', 
    url: 'http://www.zhoutian.com', 
    tags: ['mongodb', 'database', 'NoSQL'], 
    likes: 100 
})

使用 find() 函数查询数据:

> db.mycollection.find() 
{ "_id" : ObjectId("56066169ade2f21f36b03137"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 } 

{ "_id" : ObjectId("5606616dade2f21f36b03138"), "title" : "MongoDB 教程","description" : "MongoDB 是一个 Nosql 数据库", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }

接下来我们移除 title 为 'MongoDB 教程' 的文档:

>db.mycollection.remove({'title':'MongoDB 教程'}) 
WriteResult({ "nRemoved" : 2 }) # 删除了两条数据 
>db.mycollection.find() 
…… # 没有数据

如果你只想删除第一条找到的记录可以设置 justOne 为 1,如下所示:

>db.COLLECTION_NAME.remove(DELETION_CRITERIA,1)

如果你想删除所有数据,可以使用以下方式:

>db.COLLECTION_NAME.remove({}) 
>db.COLLECTION_NAME.find() 
>

2.4.8 查询文档

MongoDB 查询文档使用 find() 方法。find() 方法以非结构化的方式来显示所有文档。MongoDB 查询数据的语法格式如下:

db.COLLECTION_NAME.find(query, projection)

query :可选,使用查询操作符指定查询条件
projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值,只需省略该参数即可(默认省略)。
如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:

>db.COLLECTION_NAME.find().pretty()

以下实例我们查询了集合 mycollection 中的数据:

> db.mycollection.find().pretty() 
{ 
    "_id" : ObjectId("56063f17ade2f21f36b03133"), 
    "title" : "MongoDB 教程", 
    "description" : "MongoDB 是一个 Nosql 数据库", 
    "by" : "周天", 
    "url" : "http://www.zhoutian.com", 
    "tags" : ["mongodb", "database", "NoSQL" ],
    "likes" : 100 
}

通过下表可以更好的理解 MongoDB 的条件语句查询:

操作 格式 范例 RDBMS中的类似语句
等于 {:} db.col.find({"by":"教程"}).pretty() where by = '教程'
小于 {:{$lt:}} db.col.find({"likes":{$lt:50}}).pretty() where likes < 50
小于或等于 {:{$lte:}} db.col.find({"likes":{$lte:50}}).pretty() where likes <= 50
大于 {:{$gt:}} db.col.find({"likes":{$gt:50}}).pretty() where likes > 50
大于或等于 {:{$gte:}} db.col.find({"likes":{$gte:50}}).pretty() where likes >= 50
不等于 {:{$ne:}} db.col.find({"likes":{$ne:50}}).pretty() where likes != 50

MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。
语法格式如下:

>db.COLLECTION_NAME.find({key1:value1, key2:value2}).pretty()
> db.mycollection.find({"by":"周天", "title":"MongoDB 教程"}).pretty()

MongoDB OR 条件语句使用了关键字 $or,语法格式如下:

>db.COLLECTION_NAME.find(
   {
      $or: [
         {key1: value1}, {key2:value2}
      ]
   }
).pretty()
>db.mycollection.find({$or:[{"by":"周天"},{"title": "MongoDB 教程"}]}).pretty()

以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: 'where likes>50 AND (by = '周天' OR title = 'MongoDB 教程')'

>db.mycollection.find({"likes": {$gt:50}, $or: [{"by": "周天"},{"title": "MongoDB 教程"}]}).pretty()

如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。limit()方法基本语法如下所示:

>db.COLLECTION_NAME.find().limit(NUMBER)

集合 mycollection 中的数据如下:

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "php" ], "likes" : 200 } 
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "java" ], "likes" : 150 }
{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "mongodb" ], "likes" : 100 }

以下实例为显示查询文档中的两条记录:

> db.mycollection .find({},{"title":1,_id:0}).limit(2) 
{ "title" : "PHP 教程" } 
{ "title" : "Java 教程" } 
>

注:如果你们没有指定limit()方法中的参数则显示集合中的所有数据。
我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。skip() 方法脚本语法格式如下:

>db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

以下实例只会显示第二条文档数据

>db.mycollection.find({},{"title":1,_id:0}).limit(1).skip(1) 
{ "title" : "Java 教程" } 
>

注:skip()方法默认参数为 0 。

在 MongoDB 中使用 sort() 方法对数据(文档)进行排序,sort() 方法可以通过参数指定排序的字段,并使用1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。sort()方法基本语法如下所示:

>db.COLLECTION_NAME.find().sort({KEY:1})

mycollection集合中的数据如下:

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "php" ], "likes" : 200 } 
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "java" ], "likes" : 150 } 
{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "周天", "url" : "http://www.zhoutian.com", "tags" : [ "mongodb" ], "likes" : 100 }

以下实例演示了 mycollection 集合中的数据按字段 likes 的降序排列:

>db.mycollection.find({},{"title":1,_id:0}).sort({"likes":-1}) 
{ "title" : "PHP 教程" } 
{ "title" : "Java 教程" } 
{ "title" : "MongoDB 教程" } 
>

$type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。
如果想获取 "col" 集合中 title 为 String 的数据,你可以使用以下命令:

db.col.find({"title" : {$type : 2}})
或
db.col.find({"title" : {$type : 'string'}})

2.4.9 聚合查询

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。 MongoDB中聚合的方法使用aggregate()。
基本语法为:

>db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

为了便于理解,先将常见的mongo的聚合表达式和mysql的查询做下类比:

表达式 SQL操作 描述
$group group by 分组
$sum count()、sum() 计算总和。
$avg avg() 计算平均值
$min min() 获取集合中所有文档对应值得最小值。
$max max() 获取集合中所有文档对应值得最大值。
$match where、having 查询条件
$sort order by 排序
$limit limit 取条数
$project select 选择
$lookup(v3.2 新增) join 连接

下面以一个案例来演示,集合中的数据如下

> db.mycollection.find() 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854cf"), "name" : "zhaoyun1", "age" : 1, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d0"), "name" : "zhaoyun2", "age" : 2, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d1"), "name" : "zhaoyun3", "age" : 3, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d2"), "name" : "zhaoyun4", "age" : 4, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d3"), "name" : "zhaoyun5", "age" : 5, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d4"), "name" : "zhaoyun6", "age" : 6, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d5"), "name" : "zhaoyun7", "age" : 7, "city" : "TJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d6"), "name" : "zhaoyun8", "age" : 8, "city" : "TJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d7"), "name" : "zhaoyun9", "age" : 9, "city" : "TJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d8"), "name" : "zhaoyun10", "age" : 10, "city" : "TJ"}

$group
按照城市分组对年龄进行求和

> db.mycollection.aggregate([{$group : {_id : "$city", snum : {$sum : "$age"}}}]) 
{ "_id" : "TJ", "snum" : 34 } 
{ "_id" : "BJ", "snum" : 21 }

$sum
按照城市分组对年龄进行求和

> db.mycollection.aggregate([{$group : {_id : "$city", snum : {$sum : "$age"}}}]) 
{ "_id" : "TJ", "snum" : 34 } 
{ "_id" : "BJ", "snum" : 21 }

按照城市分组求总个数

> db.mycollection.aggregate([{$group : {_id : "$city", sc : {$sum : 1}}}]) 
{ "_id" : "TJ", "sc" : 4 } 
{ "_id" : "BJ", "sc" : 6 }

$avg
按照城市分组对年龄进行求平均

> db.mycollection.aggregate([{$group : {_id : "$city", avg : {$avg : "$age"}}}]) 
{ "_id" : "TJ", "avg" : 8.5 } 
{ "_id" : "BJ", "avg" : 3.5 }

$min
按照城市分组获得每组最小年龄

> db.mycollection.aggregate([{$group : {_id : "$city", min : {$min : "$age"}}}]) 
{ "_id" : "TJ", "min" : 7 } 
{ "_id" : "BJ", "min" : 1 }

$max
按照城市分组获得每组最大年龄

> db.mycollection.aggregate([{$group : {_id : "$city", max : {$max : "$age"}}}]) 
{ "_id" : "TJ", "max" : 10 } 
{ "_id" : "BJ", "max" : 6 }

$match
取年龄大于3并且小于10的记录再按照城市分组求个数和

> db.mycollection.aggregate( [{ $match : { age : { $gt : 3, $lte : 10 } } },{ $group: { _id: "$city",count: { $sum: 1 } } }] ); 
{ "_id" : "TJ", "count" : 4 } 
{ "_id" : "BJ", "count" : 3 }

$sort
按照年龄的倒序显示

> db.mycollection.aggregate( [{ $sort:{ age :-1}}] ) 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d8"), "name" : "zhaoyun10", "age" : 10, "city" : "TJ"}
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d7"), "name" : "zhaoyun9", "age" : 9, "city" : "TJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d6"), "name" : "zhaoyun8", "age" : 8, "city" : "TJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d5"), "name" : "zhaoyun7", "age" : 7, "city" : "TJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d4"), "name" : "zhaoyun6", "age" : 6, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d3"), "name" : "zhaoyun5", "age" : 5, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d2"), "name" : "zhaoyun4", "age" : 4, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d1"), "name" : "zhaoyun3", "age" : 3, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d0"), "name" : "zhaoyun2", "age" : 2, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854cf"), "name" : "zhaoyun1", "age" : 1, "city" : "BJ" }

$limit
取得前5条数据

> db.mycollection.aggregate( { $limit:5} ) 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854cf"), "name" : "zhaoyun1", "age" : 1, "city" : "BJ" }
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d0"), "name" : "zhaoyun2", "age" : 2, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d1"), "name" : "zhaoyun3", "age" : 3, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d2"), "name" : "zhaoyun4", "age" : 4, "city" : "BJ" } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d3"), "name" : "zhaoyun5", "age" : 5, "city" : "BJ" }

$project
不显示_id字段,显示 name和city字段

> db.mycollection.aggregate( [ {$project :{_id:0,name:1,city:1} } ] ) 
{ "name" : "zhaoyun1", "city" : "BJ" } 
{ "name" : "zhaoyun2", "city" : "BJ" } 
{ "name" : "zhaoyun3", "city" : "BJ" } 
{ "name" : "zhaoyun4", "city" : "BJ" } 
{ "name" : "zhaoyun5", "city" : "BJ" } 
{ "name" : "zhaoyun6", "city" : "BJ" } 
{ "name" : "zhaoyun7", "city" : "TJ" } 
{ "name" : "zhaoyun8", "city" : "TJ" } 
{ "name" : "zhaoyun9", "city" : "TJ" } 
{ "name" : "zhaoyun10", "city" : "TJ" }

$lookup
用于多文档关联,类似于SQL的多表关联
建立新的集合

> db.sex.find() 
{ "_id" : 1, "name" : "male" } 
{ "_id" : 2, "name" : "female" }

给mycollection新增字段sex

> db.mycollection.update({},{$set:{sex:1}},{multi:1}) 
WriteResult({ "nMatched" : 10, "nUpserted" : 0, "nModified" : 10 }) 
> db.mycollection.find() 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854cf"), "name" : "zhaoyun1", "age" : 1, "city" : "BJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d0"), "name" : "zhaoyun2", "age" : 2, "city" : "BJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d1"), "name" : "zhaoyun3", "age" : 3, "city" : "BJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d2"), "name" : "zhaoyun4", "age" : 4, "city" : "BJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d3"), "name" : "zhaoyun5", "age" : 5, "city" : "BJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d4"), "name" : "zhaoyun6", "age" : 6, "city" : "BJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d5"), "name" : "zhaoyun7", "age" : 7, "city" : "TJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d6"), "name" : "zhaoyun8", "age" : 8, "city" : "TJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d7"), "name" : "zhaoyun9", "age" : 9, "city" : "TJ", "sex" : 1 } 
{ "_id" : ObjectId("5cceaf55e7a4cf28dc5854d8"), "name" : "zhaoyun10", "age" : 10, "city" : "TJ", "sex" : 1 }

将两个文档关联

> db.mycollection.aggregate([{$lookup: {from:"sex",localField:"sex",foreignField:"_id",as:"docs"}},{$project: {"_id":0}}])
{ "name" : "zhaoyun1", "age" : 1, "city" : "BJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun2", "age" : 2, "city" : "BJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] }
{ "name" : "zhaoyun3", "age" : 3, "city" : "BJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun4", "age" : 4, "city" : "BJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun5", "age" : 5, "city" : "BJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun6", "age" : 6, "city" : "BJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun7", "age" : 7, "city" : "TJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun8", "age" : 8, "city" : "TJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun9", "age" : 9, "city" : "TJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] } 
{ "name" : "zhaoyun10", "age" : 10, "city" : "TJ", "sex" : 1, "docs" : [ { "_id" : 1, "name" : "male" } ] }

mycollection中的数据字段sex关联sex中的数据字段_id,在mycollection中显示sex对应的中文,显示在docs中,不显示mycollection中的__id
$lookup中的参数:
from:需要关联的表【sex】
localField: 【mycollection】表需要关联的键。
foreignField:【sex】的matching key 主键
as:对应的外键集合的数据,【因为是一对多的】

3 MongoDB Java客户端

3.1 mongodb-java-API

<dependency>
    <groupId>org.mongodb</groupId> 
    <artifactId>mongo-java-driver</artifactId> 
    <version>3.10.1</version> 
</dependency>
public class MongodbDemo { 
   
    private MongoClient client; 
    
    @Before public void init() { 
        client = new MongoClient("192.168.10.135",27017); 
    }
    
    @Test public void connectDB() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        System.out.println("connect successful----"+database.getName()); 
    }
    
    @Test public void createCollection() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        database.createCollection("mycollection"); 
        System.out.println("集合创建成功"); 
    }
    
    @Test public void getCollection() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        MongoCollection<Document> collection = database.getCollection("mycollection");
        System.out.println("获取集合成功:"+collection.getNamespace());
        MongoIterable<String> collectionNames = database.listCollectionNames(); 
        for (String string : collectionNames) { 
            System.out.println("collection name : "+ string); 
        } 
    }
    
    @Test public void insertDocument() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        MongoCollection<Document> collection = database.getCollection("mycollection");
        Document document = new Document("name", "James"); 
        document.append("age", 34); 
        document.append("sex", "男"); 
        Document document2 = new Document("name", "wade"); 
        document2.append("age", 36); 
        document2.append("sex", "男"); 
        Document document3 = new Document("name", "cp3"); 
        document3.append("age", 32); 
        document3.append("sex", "男"); 
        List<Document> documents = new ArrayList<>(); 
        documents.add(document); 
        documents.add(document2); 
        documents.add(document3); 
        collection.insertMany(documents);
        System.out.println("文档插入成功"); 
    }
    
    @Test public void findDocuments() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        MongoCollection<Document> collection = database.getCollection("mycollection");
        FindIterable<Document> iterable = collection.find(); 
        for (Document document : iterable) { 
            System.out.println(document.toJson()); 
        } 
        // MongoCursor<Document> mongoCursor = iterable.iterator(); 
        // while (mongoCursor.hasNext()) { 
        // Document document = mongoCursor.next(); 
        // System.out.println(document.toJson()); 
        // } 
    }
    
    @Test public void updateDocuments() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        MongoCollection<Document> collection = database.getCollection("mycollection");
        collection.updateMany(
            Filters.eq("age",18), new Document("$set", new Document("age", 20))
        ); 
        FindIterable<Document> iterable = collection.find(); 
        for (Document document : iterable) { 
            System.out.println(document.toJson()); 
        } 
    }
    
    @Test public void deleteDocuments() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        MongoCollection<Document> collection = database.getCollection("mycollection"); 
        // 删除符合条件的第一个文档 
        collection.deleteOne(Filters.eq("age",20));
        collection.deleteOne(Filters.eq("name","James")); 
        // 删除符合条件的所有文档 
        collection.deleteMany(Filters.eq("age",20)); 
        FindIterable<Document> iterable = collection.find(); 
        for (Document document : iterable) { 
            System.out.println(document.toJson()); 
        } 
    }
    
    @Test
    public void testQuery1() { 
        MongoDatabase database = client.getDatabase("zhoutian"); 
        MongoCollection<Document> collection = database.getCollection("mycollection"); 
        //获取第一条记录 
        Document first = collection.find().first(); 
        //根据key值获得数据 
        System.out.println(first.get("name")); 
        //查询指定字段 
        Document qdoc=new Document(); 
        qdoc.append("_id", 0);//0是不包含字段 
        qdoc.append("name", 1);//1是包含字段
        FindIterable<Document> findIterable = collection.find().projection(qdoc); 
        for (Document document : findIterable) { 
            System.out.println(document.toJson()); 
        } 
    }
    
    @Test public void testQuery2() { 
        MongoDatabase database = client.getDatabase("zhoutian");
        MongoCollection<Document> collection = database.getCollection("mycollection"); 
        //按指定字段排序 
        Document sdoc=new Document(); 
        //按年龄的倒序 
        sdoc.append("age", -1); 
        FindIterable<Document> findIterable = collection.find().sort(sdoc); 
        for (Document document : findIterable) { 
            System.out.println(document.toJson()); 
        } 
    } 
}

Filters
利用Filters可以进行条件过滤,比如:eq、ne、gt、lt、gte、lte、in、nin、and、or、not、nor、exists、type、mod、regex、text、where、all、elemMatch、size、bitsAllClear、bitsAllSet、bitsAnyClear、bitsAnySet、geoWithin、geoWithin、geoWithinBox、geoWithinPolygon、geoWithinCenter、geoWithinCenterSphere、geoIntersects、near、nearSphere 等。
需要引入com.mongodb.client.model.Filters类

介绍几个常用的过滤操作。

@Test public void testFilters1() { 
    MongoDatabase database = client.getDatabase("zhoutian");
    MongoCollection<Document> collection = database.getCollection("mycollection"); 
    //获得name等于zhaoyun1的记录 
    FindIterable<Document> findIterable =collection.find(Filters.eq("name", "zhaoyun1"));
    for (Document document : findIterable) { 
        System.out.println(document.toJson()); 
    }  
}

@Test public void testFilters2() { 
    MongoDatabase database = client.getDatabase("zhoutian");
    MongoCollection<Document> collection = database.getCollection("mycollection"); 
    //获得age大于3的记录 
    FindIterable<Document> findIterable =collection.find(Filters.gt("age", 3)); 
    for (Document document : findIterable) { 
        System.out.println(document.toJson()); 
    } 
}

@Testpublic void testFilters3() {
    MongoDatabase database = client.getDatabase("zhoutian");
    MongoCollection<Document> collection = database.getCollection("mycollection"); 
    //获得age大于3的记录 或者 name=zhaoyun1 
    FindIterable<Document> findIterable = 
        collection.find(Filters.or(Filters.eq("name", "zhaoyun1"),Filters.gt("age", 3))); 
    for (Document document : findIterable) { 
        System.out.println(document.toJson());
    } 
}

BasicDBObject
利用BasicDBObject可以实现模糊查询和分页查询

@Test public void testLike() { 
    MongoDatabase database = client.getDatabase("zhoutian");
    MongoCollection<Document> collection = database.getCollection("mycollection"); 
    //利用正则 模糊匹配 包含zhaoyun1 
    Pattern pattern = Pattern.compile("^.*zhaoyun1.*$", Pattern.CASE_INSENSITIVE);
    BasicDBObject query = new BasicDBObject(); 
    //name包含zhaoyun1 
    query.put("name",pattern); 
    FindIterable<Document> findIterable = collection.find(query); 
    for (Document document : findIterable) { 
        System.out.println(document.toJson()); 
    } 
}

@Test public void testPage() { 
    MongoDatabase database = client.getDatabase("zhoutian");
    MongoCollection<Document> collection = database.getCollection("mycollection"); 
    //利用正则 模糊匹配 name包含zhaoyun1 
    Pattern pattern = Pattern.compile("^.*zhaoyun.*$", Pattern.CASE_INSENSITIVE); 
    //查询条件 
    BasicDBObject query = new BasicDBObject(); 
    query.put("name",pattern);
    //排序 
    BasicDBObject sort = new BasicDBObject(); 
    //按年龄倒序 
    sort.put("age", -1); 
    //获得总条数 
    System.out.println("共计:"+collection.count()+"条记录"); 
    /** 取出第1至3条记录 
    * query : 查询条件 
    * sort: 排序 
    * skip : 起始数 从0开始 
    * limit:取几条 
    */ 
    FindIterable<Document> findIterable =           collection.find(query).sort(sort).skip(0).limit(3); 
    for (Document document : findIterable) { 
        System.out.println(document.toJson()); 
    } 
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容