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的学习和避坑到这里就结束了,感谢你们的阅读,欢迎点评。若是你以为不错,能够"推荐"一下。也能够"关注"我,得到更多丰富的知识。