1 MongoDB簡介
1.1 什麼是MongoDB
MongoDB 是一個跨平臺的,面向文檔的數據庫,是當前 NoSQL 數據庫產品中最熱門的一種。它介於關係數據庫和非關係數據庫之間,是非關係數據庫當中功能最豐富,最像關係數據庫的產品。它支持的數據結構非常鬆散,是類似 JSON 的 BSON 格式,因此可以存儲比較複雜的數據類型,MongoDB 的官方網站地址:http://www.mongodb.org/。
1.2 MongoDB特點
對於數據量大、寫入操作頻繁、價值較低這樣的數據,我們更適合使用MongoDB來實現數據的存儲,MongoDB 最大的特點是他支持的查詢語言非常強大,其語法有點類似於面向對象的查詢語言,幾乎可以實現類似關係數據庫單表查詢的絕大部分功能,而且還支持對數據建立索引。它是一個面向集合的,模式自由的文檔型數據庫,具體特點總結如下:
(1)面向集合存儲,易於存儲對象類型的數據
(2)模式自由
(3)支持動態查詢
(4)支持完全索引,包含內部對象
(5)支持複製和故障恢復
(6)使用高效的二進制數據存儲,包括大型對象(如視頻等)
(7)自動處理碎片,以支持雲計算層次的擴展性
(8)支持 Python,PHP,Ruby,Java,C,C#,Javascript,Perl 及 C++語言的驅動程序,社區中也提供了對 Erlang 及.NET 等平臺的驅動程序
(9) 文件存儲格式爲 BSON(一種 JSON 的擴展)
1.3 MongoDB體系結構
MongoDB 的邏輯結構是一種層次結構。主要由:文檔(document)、集合(collection)、數據庫(database)這三部分組成的。邏輯結構是面向用戶的,用戶使用 MongoDB 開發應用程序使用的就是邏輯結構。
(1)MongoDB 的文檔(document),相當於關係數據庫中的一行記錄。
(2)多個文檔組成一個集合(collection),相當於關係數據庫的表。
(3)多個集合(collection),邏輯上組織在一起,就是數據庫(database)。
(4)一個 MongoDB 實例支持多個數據庫(database)。
文檔(document)、集合(collection)、數據庫(database)的層次結構如下圖:
下表是MongoDB與MySQL數據庫邏輯結構概念的對比
MongoDb | 關係型數據庫Mysql |
數據庫(databases) | 數據庫(databases) |
集合(collections) | 表(table) |
文檔(document) | 行(row) |
1.5 數據類型
- null:用於表示空值或者不存在的字段,{“x”:null}
- 布爾型:布爾類型有兩個值true和false,{“x”:true}
- 數值:shell默認使用64爲浮點型數值。{“x”:3.14}或{“x”:3}。
- 對於整型值:可以使用NumberInt(4字節符號整數)或NumberLong(8字節符號整數),{“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)}
- 字符串:UTF-8字符串都可以表示爲字符串類型的數據,{“x”:“呵呵”}
- 日期:日期被存儲爲自新紀元依賴經過的毫秒數,不存儲時區,{“x”:new Date()}
- 正則表達式:查詢時,使用正則表達式作爲限定條件,語法與JavaScript的正則表達式相同,{“x”:/[abc]/}
- 數組:數據列表或數據集可以表示爲數組,{“x”: [“a“,“b”,”c”]}
- 內嵌文檔:文檔可以嵌套其他文檔,被嵌套的文檔作爲值來處理,{“x”:{“y”:3 }}
- 對象Id:對象id是一個12字節的字符串,是文檔的唯一標識,{“x”: objectId() }
- 二進制數據:二進制數據是一個任意字節的字符串。它不能直接在shell中使用。如果要
- 將非utf-字符保存到數據庫中,二進制數據是唯一的方式。
- 代碼:查詢和文檔中可以包括任何JavaScript代碼,{“x”:function(){/…/}}
2 走進MongoDB
2.1 MongoDB安裝與啓動
MongoDB 預編譯二進制包下載地址:https://www.mongodb.com/download-center/community
安裝過程中,你可以通過點擊 "Custom(自定義)" 按鈕來設置你的安裝目錄。(4.0以下版本安裝方式)
點擊下一步直到安裝完成,爲了方便每次啓動,我將MongoDB安裝後的bin目錄設置到環境變量path中
① 首先打開命令提示符,創建一個用於存放數據的目錄
md d:\data
② 啓動服務(服務器端)
mongod ‐‐dbpath=d:\data
啓動日誌
在啓動信息中可以看到,mongoDB的默認端口是27017,如果我們想改變默認的啓動端口,可以通過--port來指定端口。
③ 在命令提示符輸入以下命令即可連接上 MongoDB(客戶端)
mongo
④ 退出mongodb
exit
2.2 Docker 環境下MongoDB安裝
在宿主機創建mongo容器
docker run ‐di ‐‐name=demo_mongo ‐p 27017:27017 mongo
遠程連接
mongo 192.168.92.131
2.3 常用命令
以吐槽表spit爲例演示MongoDB常用命令
字段名稱 | 字段含義 | 字段類型 | 備註 |
---|---|---|---|
_id | ID | 文本 | |
content | 吐槽內容 | 文本 | |
publishtime | 發佈日期 | 日期 | |
userid | 發佈人ID | 文本 | |
nickname | 發佈人暱稱 | 文本 | |
visits | 瀏覽量 | 整型 | |
thumbup | 點贊數 | 整型 | |
share | 分享數 | 整型 | |
comment | 回覆數 | 整型 | |
state | 是否可見 | 文本 | |
parentid | 上級ID | 文本 |
- 選擇和創建數據庫, 如果不存在就自動創建
use 數據庫名稱
use spitdb
- 插入集合和文檔,集合如果不存在就自動創建
db.集合名稱.insert(文檔數據)
db.spit.insert({content:"內容1",userid:"7774",nickname:"云云",visits:NumberInt(60)})
- 查詢集合
db.集合名稱.find()
db.spit.find()
每條文檔會有一個叫_id的字段,這個相當於關係數據庫中表的主鍵,當你在插入文檔記錄時沒有指定該字段,MongoDB會自動創建,其類型是ObjectID類型。如果我們在插入文檔記錄時指定該字段也可以,其類型可以是ObjectID類型,也可以是MongoDB支持的任意類型。如下:
db.spit.insert({_id:"1",content:"內容123",userid:"232232",nickname:"小明"})
- 條件查詢
db.spit.find(條件)
//查詢userid爲232232的記錄
db.spit.find({userid:"232232"})
如果你只需要返回符合條件的第一條數據,我們可以使用findOne命令來實現
db.spit.findOne({nickname:"小明"})
如果你想返回指定條數的記錄,可以在find方法後調用limit來返回結果,例如:
db.spit.find({nickname:"小明"}).limit(3)
- 修改文檔
db.集合名稱.update(條件,修改後的數據)
執行後,我們會發現,這條文檔除了visits字段其它字段都不見了,爲了解決這個問題,我們需要使用修改器$set來實現,命令如下:
db.spit.update({_id:"2"},{$set:{visits:NumberInt(2000)}})
- 列值增長
如果我們想實現對某列值在原有值的基礎上進行增加或減少,可以使用$inc運算符來實現
db.spit.update({_id:"1"},{$inc:{visits:NumberInt(1)}})
- 刪除文檔
db.集合名稱.remove(條件)
//刪除userid=1211的文檔
db.spit.remove({userid:"1211"})
以下語句可以將數據全部刪除,請慎用
db.spit.remove({})
- 統計條數
統計記錄條件使用count()方法。以下語句統計spit集合的記錄數
db.spit.count()//統計全部
db.spit.count({userid:"1013"})//條件統計
- 模糊查詢
MongoDB的模糊查詢是通過正則表達式的方式實現的。格式爲:
/模糊查詢字符串/
例如,我要查詢吐槽內容包含“內容”的所有文檔,代碼如下:
db.spit.find({content:/內容/})
如果要查詢吐槽暱稱中以“小”開頭的,代碼如下:
db.spit.find({nickname:/^小/})
- 大於 小於 不等於
<, <=, >, >= 這個操作符也是很常用的,格式如下:
db.集合名稱.find({ "field" : { $gt: value }}) // 大於: field > value
db.集合名稱.find({ "field" : { $lt: value }}) // 小於: field < value
db.集合名稱.find({ "field" : { $gte: value }}) // 大於等於: field >= value
db.集合名稱.find({ "field" : { $lte: value }}) // 小於等於: field <= value
db.集合名稱.find({ "field" : { $ne: value }}) // 不等於: field != value
示例:查詢吐槽瀏覽量大於1000的記錄
db.spit.find({visits:{$gt:1000}})
- 包含與不包含
包含使用$in操作符,示例:查詢吐槽集合中userid字段包含1013和1014的文檔
db.spit.find({userid:{$in:["1013","1014"]}})
不包含使用$nin操作符。示例:查詢吐槽集合中userid字段不包含1013和1014的文檔
db.spit.find({userid:{$nin:["1013","1014"]}})
- 條件連接
我們如果需要查詢同時滿足兩個以上條件,需要使用$and操作符將條件進行關聯。(相當於SQL的and)
格式爲:$and:[ { },{ },{ } ]
示例:查詢吐槽集合中visits大於等於1000 並且小於2000的文檔
db.spit.find({$and:[{visits:{$lt:2000}},{visits:{$gte:1000}}]})
如果兩個以上條件之間是或者的關係,我們使用$or操作符進行關聯,與前面and的使用方式相同
格式爲:$or:[ { },{ },{ } ]
示例:查詢吐槽集合中userid爲1013,或者瀏覽量小於2000的文檔記錄
db.spit.find({$or:[{userid:"1013"},{visits:{$lt:2000}}]})
3 Java操作MongoDB
mongodb-driver是mongo官方推出的java連接mongoDB的驅動包,相當於JDBC驅動,我們通過一個入門的案例來了解mongodb-driver的基本使用
① 查詢全部記錄
- 創建工程 mongoDemo, 引入依賴
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.8.2</version>
</dependency>
- 創建測試類
public class MongoDemo {
public static void main(String[] args) {
//創建連接
MongoClient mongoClient = new MongoClient("192.168.92.132");
//獲取數據庫
MongoDatabase spitdb = mongoClient.getDatabase("spitdb");
//獲取集合
MongoCollection<Document> spits = spitdb.getCollection("spit");
//獲取文檔數據集合
FindIterable<Document> documents = spits.find();
//遍歷集合
for(Document document:documents){
//取值時注意值的類型,不然會報類型轉換錯誤
System.out.println("id:"+document.getObjectId("_id"));
System.out.println("內容:"+document.getString("content"));
System.out.println("瀏覽量:"+document.getInteger("visits"));
}
//關閉連接
mongoClient.close();
}
}
② 條件查詢
BasicDBObject對象:表示一個具體的記錄,BasicDBObject實現了DBObject,是keyvalue的數據結構,用起來和HashMap是基本一致的。
- 查詢userid爲2732的記錄
public static void main(String[] args) {
//創建連接
MongoClient mongoClient = new MongoClient("192.168.92.132");
//獲取數據庫
MongoDatabase spitdb = mongoClient.getDatabase("spitdb");
//獲取集合
MongoCollection<Document> spits = spitdb.getCollection("spit");
//構建條件
BasicDBObject bosn = new BasicDBObject();
bosn.put("userid","2732");
//查詢符合條件的文檔記錄
FindIterable<Document> documents = spits.find(bosn);
for(Document document:documents){
//取值時注意值的類型,不然會報類型轉換錯誤
System.out.println("id:"+document.getObjectId("_id"));
System.out.println("內容:"+document.getString("content"));
System.out.println("瀏覽量:"+document.getInteger("visits"));
}
//關閉連接
mongoClient.close();
}
- 查詢瀏覽量大於1000的記錄
public static void main(String[] args) {
//創建連接
MongoClient mongoClient = new MongoClient("192.168.92.132");
//獲取數據庫
MongoDatabase spitdb = mongoClient.getDatabase("spitdb");
//獲取集合
MongoCollection<Document> spits = spitdb.getCollection("spit");
//構建條件
BasicDBObject bosn = new BasicDBObject("visits",new BasicDBObject("$gt",1000));
//查詢符合條件的文檔記錄
FindIterable<Document> documents = spits.find(bosn);
for(Document document:documents){
//取值時注意值的類型,不然會報類型轉換錯誤
System.out.println("id:"+document.getObjectId("_id"));
System.out.println("內容:"+document.getString("content"));
System.out.println("瀏覽量:"+document.getInteger("visits"));
}
//關閉連接
mongoClient.close();
}
③ 插入記錄
public static void main(String[] args) {
//創建連接
MongoClient mongoClient = new MongoClient("192.168.92.132");
//獲取數據庫
MongoDatabase spitdb = mongoClient.getDatabase("spitdb");
//獲取集合
MongoCollection<Document> spits = spitdb.getCollection("spit");
//組裝數據
Map data=new HashMap();
data.put("content","這是一個內容");
data.put("userid","3232");
data.put("nickname","明明");
data.put("visits",1560);
Document document = new Document(data);
spits.insertOne(document);
//關閉連接
mongoClient.close();
}
3 SpringDataMongoDB
SpringData家族成員之一,用於操作MongoDb的持久層框架,封裝了底層的mongodbdriver,官網主頁: https://projects.spring.io/spring-data-mongodb
3.1 採用SpringDataMongoDB框架實現吐槽
① pom.xml引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐data‐mongodb</artifactId>
</dependency>
② 創建application.yml
#配置tomcat端口
server.port: 9006
#配置mongodb數據庫連接
spring:
data:
mongodb:
host: 192.168.92.132
port: 27017
database: spitdb
③ 創建啓動類
@SpringBootApplication
public class SpitApplication {
public static void main(String[] args) {
SpringApplication.run(SpitApplication.class);
}
@Bean
public IdWorker idWorker(){
return new IdWorker(1,1);
}
}
3.2 基本增刪改查API實現
① 創建實體類
public class Spit implements Serializable {
private String _id;
private String content;//吐槽內容
private Date publishtime;//發佈日期
private String userid;//發佈人ID
private Integer visits;//瀏覽量
private Integer thumbup;//點贊數
private Integer share;//分享數
private Integer comment;//回覆數
private String state;//是否可見
private String parentid;//上級ID
// getter and setter .....
}
② 創建數據訪問接口
/
**
* 吐槽數據訪問層
* @author Administrator
* *
/
public interface SpitDao extends MongoRepository<Spit, String>{
}
③ 創建業務邏輯類
@Service
public class SpitService {
@Autowired
private SpitDao spitDao;
@Autowired
private IdWorker idWorker;
/**
* 查詢全部記錄
* @return
*/
public List<Spit> findAll(){
return spitDao.findAll();
}
/**
* 根據主鍵查詢實體
* @param id
* @return
*/
public Spit findById(String id){
Spit spit = spitDao.findById(id).get();
return spit;
}
/**
* 增加
* @param spit
*/
public void add(Spit spit) {
spit.set_id(idWorker.nextId()+""); //主鍵值
spitDao.save(spit);
}
/**
* 修改
* @param spit
*/
public void update(Spit spit) {
spitDao.save(spit);
}
/**
* 刪除
* @param id
*/
public void deleteById(String id) {
spitDao.deleteById(id);
}
}
④ 創建controller類
@RestController
@CrossOrigin
@RequestMapping("/spit")
public class SpitController {
@Autowired
private SpitService spitService;
/**
* 查詢全部數據
* @return
*/
@RequestMapping(method= RequestMethod.GET)
public Result findAll(){
return new Result(true, StatusCode.OK,"查詢成功",spitService.findAll());
}
/**
* 根據ID查詢
* @param id ID
* @return
*/
@RequestMapping(value="/{id}",method=RequestMethod.GET)
public Result findOne(@PathVariable String id){
return new Result(true,StatusCode.OK,"查詢成功",spitService.findById(id));
}
/**
* 增加
* @param spit
*/
@RequestMapping(method=RequestMethod.POST)
public Result add(@RequestBody Spit spit ){
spitService.add(spit);
return new Result(true,StatusCode.OK,"增加成功");
}
/**
* 修改
* @param spit
*/
@RequestMapping(value="/{id}",method=RequestMethod.PUT)
public Result update(@RequestBody Spit spit,@PathVariable String id )
{
spit.set_id(id);
spitService.update(spit);
return new Result(true,StatusCode.OK,"修改成功");
}
/**
* 刪除
* @param id
*/
@RequestMapping(value="/{id}",method=RequestMethod.DELETE)
public Result deleteById(@PathVariable String id ){
spitService.deleteById(id);
return new Result(true,StatusCode.OK,"刪除成功");
}
}
3.3 根據上級ID查詢吐槽列表
① SpitDao新增方法定義
/**
* 根據上級ID查詢吐槽列表(分頁)
* @param parentid
* @param pageable
* @return
*/
public Page<Spit> findByParentid(String parentid,Pageable pageable)
② SpitService新增方法
/**
* 根據上級ID查詢吐槽數據(分頁)
* 查詢一個吐槽下面的評論
*/
public PageResult<Spit> findByParentid(String pid,int page,int size){
Pageable pageable =PageRequest.of(page-1,size);
Page<Spit> pages = spitDao.findByParentid(pid, pageable);
return new PageResult<Spit>(pages.getTotalElements(),pages.getContent());
}
③ SpitController新增方法
//根據上級ID查詢吐槽數據(分頁)
@GetMapping("/comment/{parentid}/{page}/{size}")
public Result<PageResult> findByParentId(@PathVariable("parentid") String id,@PathVariable int page,@PathVariable int size){
return new Result<>(StatusCode.SUCCESS,true,"查詢成功",spitService.findByParentid(id,page,size));
}
3.4 吐槽點贊
/**
* 點贊
* @param id
*/
public void updateThumbup(String id){
Spit spit = spitDao.findById(id).get();
spit.setThumbup(spit.getThumbup()+1);
spitDao.save(spit);
}
以上方法雖然實現起來比較簡單,但是執行效率並不高,因爲我只需要將點贊數加1就可以了,沒必要查詢出所有字段修改後再更新所有字段,我們可以使用MongoTemplate類來實現對某列的操作。
① 修改SpitService
@Autowired
private MongoTemplate mongoTemplate;
/**
* 點贊
* @param id
*/
public void updateThumbup(String id){
Query query=new Query();
query.addCriteria(Criteria.where("_id").is(id));
Update update=new Update();
update.inc("thumbup",1);
mongoTemplate.updateFirst(query,update,"spit");
}
② SpitController新增方法
/**
* 點贊
* @param id
* @return
*/
@RequestMapping(value="/thumbup/{id}",method=RequestMethod.PUT)
public Result updateThumbup(@PathVariable String id){
spitService.updateThumbup(id);
return new Result(true,StatusCode.OK,"點贊成功");
}
3.5 取消點贊
我們可以通過redis控制用戶點贊或者取消點贊
① pom.xml中加入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐data‐redis</artifactId>
</dependency>
② 修改application.yml
redis:
host: 192.168.184.135
③ 修改SpitService 點贊方法
//吐槽點贊
public String updateThumbup(String id) {
String message="";
Query query = new Query();
//添加條件
query.addCriteria(Criteria.where("_id").is(id));
Update update=new Update();
String userid="7777";//模擬數據,實際應該取當前登陸的用戶
if("1".equals(redisTemplate.opsForValue().get("thumbup_"+userid+"_"+id))){
//點贊取消
update.inc("thumbup",-1);
redisTemplate.opsForValue().set("thumbup_"+userid+"_"+id,"-1");
message="點贊已取消";
}else {
//點贊
update.inc("thumbup",1);
redisTemplate.opsForValue().set("thumbup_"+userid+"_"+id,"1");
message="點贊成功";
}
mongoTemplate.updateFirst(query,update,"spit" );
return message;
}
3.6 發佈吐槽
修改SpitService的add方法
/**
* 發佈吐槽(或吐槽評論)
* @param spit
*/
public void add(Spit spit){
spit.set_id( idWorker.nextId()+"" );
spit.setPublishtime(new Date());//發佈日期
spit.setVisits(0);//瀏覽量
spit.setShare(0);//分享數
spit.setThumbup(0);//點贊數
spit.setComment(0);//回覆數
spit.setState("1");//狀態
if(spit.getParentid()!=null && !"".equals(spit.getParentid())){
//如果存在上級ID,則是評論
Query query=new Query();
query.addCriteria(Criteria.where("_id").is(spit.getParentid()));
Update update=new Update();
update.inc("comment",1);
mongoTemplate.updateFirst(query,update,"spit");
}
spitDao.save(spit);
}