MongoDB 是一把雙刃劍,它對數據結構的要求並不高。數據經過key-value的形式存儲,而value的值能夠是字符串,也能夠是文檔。因此咱們在使用的過程當中很是方便。正是這種方便給咱們埋下了一顆顆地雷。當內嵌的文檔太深,或者內嵌文檔有相同的屬性名。你會被炸得很慘。本章節經過 MongoDB簡介,Shell編程,SpringBoot整合MongoDB,工做中注意事項,四個方面介紹MongoDB的使用。讓你輕鬆入門,輕鬆避坑。還在等什麼,趕快來學習吧!java
技術:MongoDB,SpringBoot,SpringDataMongoDB
說明:本章重點介紹MongoDB的使用,對非關係型數據庫的介紹會比較簡單。完整代碼和相關sql請移步github,ths!
源碼:https://github.com/ITDragonBl...git
MongoDB 是非關係型數據庫中,最接近關係型數據庫的,文檔型數據庫。它支持的查詢功能很是強大。
MongoDB 是爲快速開發互聯網Web應用而設計的數據庫系統。他的數據模型是面向文檔的,這種文檔是一種相似於JSON的結構,準確來講是一種支持二進制的BSON(Binary JSON)結構。程序員
非關係性數據庫 也被稱爲 NoSQL(Not only sql),主要有四大類:鍵值存儲數據庫、列存儲數據庫、文檔型數據庫、圖形數據庫。以前介紹的Redis屬於鍵值存儲數據庫。github
關係型數據庫的優勢:
1 支持事務處理,事務特性:原子性、一致性、隔離性、持久性。
2 數據結構清晰,便於理解,可讀性高。
3 使用方便,有標準的sql語法。正則表達式
關係型數據庫的缺點:
1 讀寫性能相對較差,爲保證事務的一致性,須要必定的開銷。在高併發下表現的尤其突出。
2 表結構固定,不易於表後期的擴展,因此前期對錶的設計要求較高。spring
非關係型數據庫的優勢:
1 讀寫性能高,沒有保障數據的一致性。
2 表結構靈活,表結構並非固定的,經過key-value存儲數據,value又能夠存儲其餘格式的數據。sql
二者的優缺點實際上是向反的,一件事物不會憑空出現,都是在原有的基礎上作了補充和優化,二者的側重點各有不一樣。就像MySQL保障了數據的一致性,卻影響了讀寫的性能。MongoDB放棄數據的強一致性,保障了讀寫的效率。在合適的場景使用合適的數據庫,是須要咱們考慮的。
1 對於須要高度事務特性的系統,好比和錢有關的,銀行系統,金融系統。咱們要考慮使用關係型數據庫,確保數據的一致性和持久性。
2 對於那些數據並非很重要,訪問量又很大的系統,好比電商平臺的商品信息。咱們可使用非關係型數據庫來作緩存,充分提升了系統查詢的性能。mongodb
這裏對銀行和金融我想抱怨兩句:
第一:投資理財千萬不要選擇小平臺金融公司,收益再高都是虛假的,多半都是圈錢跑路的,錢的教訓。
第二:某些銀行APP顯示的金額不是實時的。16年某生銀行卡轉入40萬,但在個人總資產界面並無轉入的金額,嚇得我一身冷汗。顫抖着雙手給客服打了幾個電話才知道,某生銀行APP的總資產界面數據是統計前一天的。直到次日,金額才顯示正確。今後我再也沒有用某生的銀行卡。某商的信用卡也是同樣,還了錢金額並無減下來。不知道如今有沒有改。數據庫
有在銀行工做的朋友,可否告訴我這樣設計的緣由是啥?難道用戶體驗不重要?仍是要體現客服的價值?反正,這鍋咱們程序員不背。apache
Mongodb的查詢功能十分強大,有find() 和 findOne()。支持的查詢條件有:$lt、$lte、$gt、$gte、$ne、$or、$in、$nin、$not、$exists、$and、正則表達式等。
查詢建議:
1 查詢全部數據,建議使用分頁查詢。
2 查詢key建議用引號,對象的屬性能夠省略引號,內嵌的對象屬性不能省略。好比下面的name能夠省略,但address.province則不能。
3 儘可能少用$or, $in 查詢,效率很低。
// 查詢全部(不推薦,通常使用分頁查詢) db.itdragonuser.find(); {"_id":ObjectId("5a9bbefa2f3fdfdf540a1be7"),"name":"ITDragon","age":25,"address":{"province":"廣東省","city":"深圳"},"ability":["JAVA"]} // 等於查詢 db.itdragonuser.find({"name":"ITDragon"}); // 模糊查詢 db.itdragonuser.find({"name":/ITDragon/}); // 或者查詢 db.itdragonuser.find({$or:[{"address.province":"湖北"},{"address.province":"湖南"}]}); // 包含查詢(包含了JAVA或者HTML) db.itdragonuser.find({"ability":{$in:["JAVA","HTML"]}}); // 不包含查詢(JAVA和HTML都不包含) db.itdragonuser.find({"ability":{$nin:["JAVA","HTML"]}}); // 範圍查詢$gt , $lt , $gte , $lte , $ne db.itdragonuser.find({"age":{$gt:25}}); // 正則表達式查詢(查詢以WeiXin結尾的數據) db.itdragonuser.find({"name":/WeiXin$/}); // 按照條件統計數據 db.itdragonuser.count({"name":/ITDragon/}); // 過濾重複內容(打印不重複的name值) db.itdragonuser.distinct("name"); // sort:排序(1表示升序 -1表示降序),skip:跳過指定數量,limit:每頁查詢數量 db.itdragonuser.find().sort({"age":1}).skip(2).limit(3); // 字段投影,(0表示不顯示,1表示顯示) db.itdragonuser.find({},{_id:0,name:1,address:1,aliblity:1});
插入數據比較簡單,insert() 能夠向集合插入一個或多個文檔,而insertOne() 和 insertMany() 細化了insert() 方法,語法是同樣的,命名規則上更清晰。
插入建議:
1 插入數據不能破壞原有的數據結構,形成沒必要要的麻煩。
2 批量插入數據,儘可能一次執行多個文檔,而不是多個文檔執行屢次方法。
// 插入一條數據,類型有字符串,數字,對象,集合 db.itdragonuser.insert({"name":"ITDragon","age":24,"address":{"province":"廣東","city":"深圳"},"ability":["JAVA","HTML"]}) // 插入多條數據 db.itdragonuser.insert([ {"name":"ITDragon","age":24,"address":{"province":"廣東","city":"深圳"},"ability":["JAVA","HTML"]}, {"name":"ITDragonGit","age":24,"address":{"province":"湖北","city":"武漢"},"ability":["JAVA","HTML","GIT"]} ])
更新數據時,須要確保value的數據結構,是字符串,是集合,仍是對象,不能破壞原有的數據結構。儘可能使用修改器來幫忙完成操做。
經常使用的修改器:
$inc : 數值類型屬性自增
$set : 用來修改文檔中的指定屬性
$unset : 用來刪除文檔的指定屬性
$push : 向數組屬性添加數據
$addToSet : 向數組添加不重複的數據
更新建議:
1 更新數據不能破壞原有的數據結構。
2 正確使用修改器完成更新操做。
// 更新字符串屬性 db.itdragonuser.update({"name":"ITDragonGit"},{$set:{"name":"ITDragon"}}); // 更新對象屬性 db.itdragonuser.update({"name":"ITDragon"},{$set:{"address.province":"廣東省"}}); // 更新集合屬性 db.itdragonuser.update({"name":"ITDragon"},{$push:{"ability":"MONGODB"}}); // 批量更新屬性 db.itdragonuser.updateMany({"name":"ITDragon"},{$set:{"age":25}}); // 批量更新屬性,加參數multi:true db.itdragonuser.update({"name":"ITDragon"},{$set:{"age":25}},{multi:true});
刪除數據是一個很是謹慎的操做,實際開發中不會物理刪除數據,只是邏輯刪除。方便數據恢復和大數據分析。這裏只簡單介紹。
若是你以爲Spring整合MongoDB略顯麻煩,那SpringBoot整合MongoDB就是你的福音。SpringBoot旨在零配置,只需簡單的兩個步驟便可。
第一步:在pom.xml文件中添加spring-boot-starter-data-mongodb
。
<dependency> <!-- 添加對mongodb的支持 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
第二步:在application.properties文件中配置MongoDB數據庫連接地址。
連接MongoDB數據庫地址規則:spring.data.mongodb.uri=mongodb://account:password@ip:port/database
其中 account和password方即是連接數據庫的帳號和密碼。而database是須要連接的數據庫地址
# 沒有帳號密碼能夠簡寫 spring.data.mongodb.uri=mongodb://localhost:27017/itdragonstu
Spring Data給咱們提供了MongoTemplate類,極大的方便了咱們的工做,可是若每一個實體類都基於MongoTemplate重寫一套CRUD的實現類,彷佛顯得有些笨重。因而咱們能夠將其簡單的封裝一下。步驟以下
第一步:建立用戶實體類,其數據庫表名就是類名首字母小寫。
第二步:封裝MongoTemplate類,實現增刪改查,分頁,排序,主鍵自增等經常使用功能。
第三步:建立封裝類的Bean管理類,針對不一樣的實體類,須要配置不一樣的bean。
第四步:建立測試類,測試:註冊,更新,分頁,排序,查詢用戶功能。
用戶實體類有五個字段,除了主鍵ID,其餘四個分別表明四個經常使用的類型(字符串,數字,對象,集合)。爲了簡化開發,實體類建議不實用@Document註解重命名User在MongoDB數據庫中的表名。
省略get/set方法和toString方法
import java.io.Serializable; import java.util.ArrayList; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; /** * 用戶實體類 * @author itdragon */ //@Document(collection = "itdragon_user") 若是爲了代碼的通用性,建議不要使用 public class User implements Serializable{ private static final long serialVersionUID = 1L; @Id private Long id; private String name; private Integer age; private Address address; private ArrayList ability; } public class Address{ private Long id; private String province; private String city; }
SpringData提供的MongoTemplate類,極大的方便咱們操做MongoDB數據庫。但是它的不少方法都涉及到了Class,和CollectionName。針對不一樣的實體類,咱們須要重複寫不一樣的方法。這裏,咱們進一步封裝,實現代碼的高可用。
實現的思路大體:將Class做爲一個參數,在初始化MongoTemplate的封裝類時賦值。這裏有一個約束條件是:CollectionName是Class類名的首字母小寫。
import java.util.List; import java.util.Map; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; 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.Repository; import com.mongodb.BasicDBObject; import com.mongodb.DBCollection; import com.mongodb.DBObject; @Repository @SuppressWarnings({"unchecked", "rawtypes"}) public class ITDragonMongoHelper { @Autowired(required = false) private MongoTemplate mongoTemplate; private Class entityClass; // 實體類 private String collectionName; // 數據庫表名 private String orderAscField; // 升序字段 private String orderDescField; // 降序字段 private static final String ID = "id"; private static final String MONGODB_ID = "_id"; public ITDragonMongoHelper() { } public ITDragonMongoHelper(Class entityClass) { this.entityClass = entityClass; this.collectionName = _getCollectionName(); } public ITDragonMongoHelper(Class entityClass, String collectionName) { this.entityClass = entityClass; this.collectionName = collectionName; } /** * @Title save * @Description 經過Map建立實體類 * @param object Map,無需自帶ID * @return */ public Boolean save(Map<String, Object> requestArgs) { try { Object object = getEntityClass().newInstance(); if (null == requestArgs.get(ID)) { requestArgs.put(ID, getNextId()); } BeanUtils.populate(object, requestArgs); saveOrUpdate(object); } catch (Exception e) { e.printStackTrace(); return Boolean.valueOf(false); } return Boolean.valueOf(true); } /** * @Title save * @Description 經過對象建立實體類 * @param object 實體類,需自帶ID * @return */ public Boolean saveOrUpdate(Object object) { try { this.mongoTemplate.save(object, this.collectionName); } catch (Exception e) { e.printStackTrace(); return Boolean.valueOf(false); } return Boolean.valueOf(true); } /** * @Title update * @Description 經過Map更新實體類具體字段,能夠減小更新出錯字段,執行的銷率更高,需嚴格要求數據結構的正確性 * @param requestArgs Map,需自帶ID, 形如:{id: idValue, name: nameValue, ....} * @return */ public Boolean update(Map<String, Object> requestArgs) { Object id = requestArgs.get(ID); if (null == id) { return Boolean.valueOf(false); } try { Update updateObj = new Update(); requestArgs.remove(ID); for (String key : requestArgs.keySet()) { updateObj.set(key, requestArgs.get(key)); } findAndModify(Criteria.where(ID).is(id), updateObj); } catch (Exception e) { e.printStackTrace(); return Boolean.valueOf(false); } return Boolean.valueOf(true); } /** * @Title find * @Description 根據查詢條件返回全部數據,不推薦 * @param criteria 查詢條件 * @return */ public List find(Criteria criteria) { Query query = new Query(criteria); _sort(query); return this.mongoTemplate.find(query, this.entityClass, this.collectionName); } /** * @Title find * @Description 根據查詢條件返回指定數量數據 * @param criteria 查詢條件 * @param pageSize 查詢數量 * @return */ public List find(Criteria criteria, Integer pageSize) { Query query = new Query(criteria).limit(pageSize.intValue()); _sort(query); return this.mongoTemplate.find(query, this.entityClass, this.collectionName); } /** * @Title find * @Description 根據查詢條件分頁返回指定數量數據 * @param criteria 查詢條件 * @param pageSize 查詢數量 * @param pageNumber 當前頁數 * @return */ public List find(Criteria criteria, Integer pageSize, Integer pageNumber) { Query query = new Query(criteria).skip((pageNumber.intValue() - 1) * pageSize.intValue()).limit(pageSize.intValue()); _sort(query); return this.mongoTemplate.find(query, this.entityClass, this.collectionName); } public Object findAndModify(Criteria criteria, Update update) { // 第一個參數是查詢條件,第二個參數是須要更新的字段,第三個參數是須要更新的對象,第四個參數是MongoDB數據庫中的表名 return this.mongoTemplate.findAndModify(new Query(criteria), update, this.entityClass, this.collectionName); } /** * @Title findById * @Description 經過ID查詢數據 * @param id 實體類ID * @return */ public Object findById(Object id) { return this.mongoTemplate.findById(id, this.entityClass, this.collectionName); } /** * @Title findOne * @Description 經過查詢條件返回一條數據 * @param id 實體類ID * @return */ public Object findOne(Criteria criteria) { Query query = new Query(criteria).limit(1); _sort(query); return this.mongoTemplate.findOne(query, this.entityClass, this.collectionName); } // id自增加 public String getNextId() { return getNextId(getCollectionName()); } public String getNextId(String seq_name) { String sequence_collection = "seq"; String sequence_field = "seq"; DBCollection seq = this.mongoTemplate.getCollection(sequence_collection); DBObject query = new BasicDBObject(); query.put(MONGODB_ID, seq_name); DBObject change = new BasicDBObject(sequence_field, Integer.valueOf(1)); DBObject update = new BasicDBObject("$inc", change); DBObject res = seq.findAndModify(query, new BasicDBObject(), new BasicDBObject(), false, update, true, true); return res.get(sequence_field).toString(); } private void _sort(Query query) { if (null != this.orderAscField) { String[] fields = this.orderAscField.split(","); for (String field : fields) { if (ID.equals(field)) { field = MONGODB_ID; } query.with(new Sort(Sort.Direction.ASC, new String[] { field })); } } else { if (null == this.orderDescField) { return; } String[] fields = this.orderDescField.split(","); for (String field : fields) { if (ID.equals(field)) { field = MONGODB_ID; } query.with(new Sort(Sort.Direction.DESC, new String[] { field })); } } } // 獲取Mongodb數據庫中的表名,若表名不是實體類首字母小寫,則會影響後續操做 private String _getCollectionName() { String className = this.entityClass.getName(); Integer lastIndex = Integer.valueOf(className.lastIndexOf(".")); className = className.substring(lastIndex.intValue() + 1); return StringUtils.uncapitalize(className); } public Class getEntityClass() { return entityClass; } public void setEntityClass(Class entityClass) { this.entityClass = entityClass; } public String getCollectionName() { return collectionName; } public void setCollectionName(String collectionName) { this.collectionName = collectionName; } public String getOrderAscField() { return orderAscField; } public void setOrderAscField(String orderAscField) { this.orderAscField = orderAscField; } public String getOrderDescField() { return orderDescField; } public void setOrderDescField(String orderDescField) { this.orderDescField = orderDescField; } }
這裏用Bean註解修飾的方法名和測試類中ITDragonMongoHelper 的變量名要保持一致。這樣才能具體知道是哪一個實體類的數據操做。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.itdragon.pojo.User; import com.itdragon.repository.ITDragonMongoHelper; /** * ITDragonMongoHelper的bean配置管理類 * @author itdragon */ @Configuration public class MongodbBeansConfig { @Bean // 該方法名很重要 public ITDragonMongoHelper userMongoHelper() { return new ITDragonMongoHelper(User.class); } }
主要測試MongoDB保存數據,更新字符串,更新數值,更新對象(文檔),更新集合,分頁查詢幾個經常使用方法。
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.test.context.junit4.SpringRunner; import com.itdragon.StartApplication; import com.itdragon.pojo.Address; import com.itdragon.pojo.User; import com.itdragon.repository.ITDragonMongoHelper; /** * @RunWith 它是一個運行器 * @RunWith(SpringRunner.class) 表示讓測試運行於Spring測試環境,不用啓動spring容器便可使用Spring環境 * @SpringBootTest(classes=StartApplication.class) 表示將StartApplication.class歸入到測試環境中,若不加這個則提示bean找不到。 * @author itdragon */ @RunWith(SpringRunner.class) @SpringBootTest(classes=StartApplication.class) public class SpringbootStudyApplicationTests { @Autowired private ITDragonMongoHelper userMongoHelper; // 命名規則:需和MongodbBeansConfig配置Bean的方法名一致 @Test public void createUser() { System.out.println("^^^^^^^^^^^^^^^^^^^^^^createUser"); for (int i = 0; i < 25; i++) { // 插入25條數據 User user = new User(); user.setId(Long.valueOf(userMongoHelper.getNextId(User.class.getName()))); user.setAge(25 + i); user.setName("itdragon-" + i); Address address = new Address(); address.setId(Long.valueOf(userMongoHelper.getNextId(Address.class.getName()))); address.setProvince("湖北省"); address.setCity("武漢市"); user.setAddress(address); ArrayList<String> ability = new ArrayList<>(); ability.add("Java"); user.setAbility(ability); userMongoHelper.saveOrUpdate(user); System.out.println("user : " + user.toString()); } } @Test public void updateUserName() { System.out.println("^^^^^^^^^^^^^^^^^^^^^^updateUserName"); Map<String, Object> updateMap = new HashMap<>(); // 查詢name爲itdragon-1的數據,將name修改成ITDragonBlog User user = (User) userMongoHelper.findOne(Criteria.where("name").is("itdragon-1")); if (null == user) { System.out.println("^^^^^^^^^^^^^^^^^^^^^^User non-existent"); return ; } updateMap.put("id", user.getId()); updateMap.put("name", "ITDragonBlog"); userMongoHelper.update(updateMap); } @Test public void updateUserAddress() { System.out.println("^^^^^^^^^^^^^^^^^^^^^^updateUserAddress"); Map<String, Object> updateMap = new HashMap<>(); User user = (User) userMongoHelper.findOne(Criteria.where("name").is("itdragon-3")); if (null == user) { System.out.println("^^^^^^^^^^^^^^^^^^^^^^User non-existent"); return ; } Address address = new Address(); address.setId(Long.valueOf(userMongoHelper.getNextId(Address.class.getName()))); address.setProvince("湖南省"); address.setCity("長沙"); updateMap.put("id", user.getId()); updateMap.put("address", address); userMongoHelper.update(updateMap); } @Test public void updateUserAbility() { System.out.println("^^^^^^^^^^^^^^^^^^^^^^updateUserAbility"); Map<String, Object> updateMap = new HashMap<>(); User user = (User) userMongoHelper.findOne(Criteria.where("name").is("itdragon-4")); if (null == user) { System.out.println("^^^^^^^^^^^^^^^^^^^^^^User non-existent");; return ; } ArrayList<String> abilitys = user.getAbility(); abilitys.add("APP"); updateMap.put("id", user.getId()); updateMap.put("ability", abilitys); userMongoHelper.update(updateMap); } @Test public void findUserPage() { System.out.println("^^^^^^^^^^^^^^^^^^^^^^findUserPage"); userMongoHelper.setOrderAscField("age"); // 排序 Integer pageSize = 5; // 每頁頁數 Integer pageNumber = 1; // 當前頁 List<User> users = userMongoHelper.find(Criteria.where("age").gt(25), pageSize, pageNumber); // 查詢age大於25的數據 for (User user : users) { System.out.println("user : " + user.toString()); } } }
MongoDB對錶結構要求不嚴,方便了咱們的開發,同時也提升了犯錯率,特別是公司來了新同事,這顆地雷隨時都會爆炸。
第一點: MongoDB經過key獲取value的值。而這個value能夠是內嵌的其餘文檔。由於沒有主外鍵的概念,使用起來很是方便。若嵌套的文檔太深,在更新數據是,須要注意不能覆蓋原來的值。好比User表中的ability是一個集合,若傳一個字符串,依然能夠更新成功,但已經破壞了數據結構。這是不少新手容易犯的錯。
第二點: 內嵌的文檔屬性名最好不要重名。舉個例子,若是User表中的address對象,也有一個name的屬性。那麼在後續寫代碼的過程當中,極容易混淆。致使數據更新異常。
第三點: 表的設計儘可能作到扁平化,單表設計能有效提升數據庫的查詢銷率。
第四點: 使用Mongoose約束數據結構,當數據結構不一致時操做失敗。
前兩點足以讓一些老輩程序員抓狂,讓新來的程序員懵圈。這也是不少開發人員喜歡又討厭MongoDB的緣由。
1 MongoDB是最接近關係型數據的非關係型數據庫中的文檔型數據庫。
2 MongoDB支持很是豐富的查詢語句,功能強大,但容易犯錯。
3 MongoDB表結構的設計需謹慎,儘可能減小嵌套層數,各嵌套的文檔屬性名儘可能避免相同。
MongoDB官方文檔: https://docs.mongodb.com
雙刃劍MongoDB的學習和避坑到這裏就結束了,感謝你們的閱讀,歡迎點評。若是你以爲不錯,能夠"推薦"一下。也能夠"關注"我,得到更多豐富的知識。