沈学 發表於 2018-3-7 21:53:00

双刃剑MongoDB的学习和避坑

<h1 id="双刃剑mongodb的学习和避坑">双刃剑MongoDB的学习和避坑</h1>
<p>MongoDB 是一把双刃剑,它对数据结构的要求并不高。数据通过key-value的形式存储,而value的值可以是字符串,也可以是文档。所以我们在使用的过程中非常方便。正是这种方便给我们埋下了一颗颗地雷。当内嵌的文档太深,或者内嵌文档有相同的属性名。你会被炸得很惨。本章节通过 MongoDB简介,Shell编程,SpringBoot整合MongoDB,工作中注意事项,四个方面介绍MongoDB的使用。让你轻松入门,轻松避坑。还在等什么,赶快来学习吧!</p>
<p>技术:MongoDB,SpringBoot,SpringDataMongoDB<br>
说明:本章重点介绍MongoDB的使用,对非关系型数据库的介绍会比较简单。完整代码和相关sql请移步github,ths!<br>
源码:https://github.com/ITDragonBlog/daydayup/tree/master/MongoDB</p>
<h2 id="mongodb-简介">MongoDB 简介</h2>
<p>MongoDB 是非关系型数据库中,最接近关系型数据库的,文档型数据库。它支持的查询功能非常强大。<br>
MongoDB 是为快速开发互联网Web应用而设计的数据库系统。他的数据模型是面向文档的,这种文档是一种类似于JSON的结构,准确来说是一种支持二进制的BSON(Binary JSON)结构。</p>
<h3 id="非关系性数据库">非关系性数据库</h3>
<p>非关系性数据库 也被称为 NoSQL(Not only sql),主要有四大类:键值存储数据库、列存储数据库、文档型数据库、图形数据库。之前介绍的Redis属于键值存储数据库。</p>
<h3 id="关系与非关系型数据库">关系与非关系型数据库</h3>
<p>关系型数据库的优点:<br>
1 支持事务处理,事务特性:原子性、一致性、隔离性、持久性。<br>
2 数据结构清晰,便于理解,可读性高。<br>
3 使用方便,有标准的sql语法。</p>
<p>关系型数据库的缺点:<br>
1 读写性能相对较差,为保证事务的一致性,需要一定的开销。在高并发下表现的尤为突出。<br>
2 表结构固定,不易于表后期的扩展,所以前期对表的设计要求较高。</p>
<p>非关系型数据库的优点:<br>
1 读写性能高,没有保障数据的一致性。<br>
2 表结构灵活,表结构并不是固定的,通过key-value存储数据,value又可以存储其他格式的数据。</p>
<p>两者的优缺点其实是向反的,一件事物不会凭空出现,都是在原有的基础上做了补充和优化,两者的侧重点各有不同。就像MySQL保障了数据的一致性,却影响了读写的性能。MongoDB放弃数据的强一致性,保障了读写的效率。在合适的场景使用合适的数据库,是需要我们考虑的。<br>
1 对于需要高度事务特性的系统,比如和钱有关的,银行系统,金融系统。我们要考虑使用关系型数据库,确保数据的一致性和持久性。<br>
2 对于那些数据并不是很重要,访问量又很大的系统,比如电商平台的商品信息。我们可以使用非关系型数据库来做缓存,充分提高了系统查询的性能。</p>
<p>这里对银行和金融我想抱怨两句:<br>
第一:投资理财千万不要选择小平台金融公司,收益再高都是虚假的,多半都是圈钱跑路的,钱的教训。<br>
第二:某些银行APP显示的金额不是实时的。16年某生银行卡转入40万,但在我的总资产界面并没有转入的金额,吓得我一身冷汗。颤抖着双手给客服打了几个电话才知道,某生银行APP的总资产界面数据是统计前一天的。直到第二天,金额才显示正确。从此我再也没有用某生的银行卡。某商的信用卡也是一样,还了钱金额并没有减下来。不知道现在有没有改。</p>
<p>有在银行工作的朋友,能否告诉我这样设计的原因是啥?难道用户体验不重要?还是要体现客服的价值?反正,这锅我们程序员不背。</p>
<h2 id="mongodb-shell-编程">Mongodb Shell 编程</h2>
<h3 id="查询数据">查询数据</h3>
<p>Mongodb的查询功能十分强大,有find() 和 findOne()。支持的查询条件有:$lt、$lte、$gt、$gte、$ne、$or、$in、$nin、$not、$exists、$and、正则表达式等。</p>
<ul>
<li>db.collection.find() 根据查询条件返回所有文档</li>
<li>db.collection.findOne() 根据查询条件返回第一个文档</li>
</ul>
<p>查询建议:<br>
1 查询所有数据,建议使用分页查询。<br>
2 查询key建议用引号,对象的属性可以省略引号,内嵌的对象属性不能省略。比如下面的name可以省略,但address.province则不能。<br>
3 尽量少用$or, $in 查询,效率很低。</p>
<pre><code class="language-sql">// 查询所有(不推荐,一般使用分页查询)
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});
</code></pre>
<h3 id="插入数据">插入数据</h3>
<p>插入数据比较简单,insert() 可以向集合插入一个或多个文档,而insertOne() 和 insertMany() 细化了insert() 方法,语法是一样的,命名规则上更清晰。</p>
<ul>
<li>db.collection.insert()         可以向集合中插入一个或多个文档</li>
<li>db.collection.insertOne() 向集合中插入一个文档</li>
<li>db.collection.insertMany()向集合中插入多个文档</li>
</ul>
<p>插入建议:<br>
1 插入数据不能破坏原有的数据结构,造成不必要的麻烦。<br>
2 批量插入数据,尽量一次执行多个文档,而不是多个文档执行多次方法。</p>
<pre><code class="language-sql">// 插入一条数据,类型有字符串,数字,对象,集合
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"]}
])
</code></pre>
<h3 id="更新数据">更新数据</h3>
<p>更新数据时,需要确保value的数据结构,是字符串,是集合,还是对象,不能破坏原有的数据结构。尽量使用修改器来帮忙完成操作。</p>
<ul>
<li>db.collection.update()        可以修改、替换集合中的一个或多个文档,默认修改第一个,若要修改多个,则需要使用multi:true</li>
<li>db.collection.updateOne()修改集合中的一个文档</li>
<li>db.collection.updateMany() 修改集合中的多个文档</li>
<li>db.collection.replaceOne() 替换集合中的一个文档</li>
</ul>
<p>常用的修改器:<br>
<strong>$inc</strong> : 数值类型属性自增<br>
<strong>$set</strong> : 用来修改文档中的指定属性<br>
<strong>$unset</strong> : 用来删除文档的指定属性<br>
<strong>$push</strong>: 向数组属性添加数据<br>
<strong>$addToSet</strong> : 向数组添加不重复的数据</p>
<p>更新建议:<br>
1 更新数据不能破坏原有的数据结构。<br>
2 正确使用修改器完成更新操作。</p>
<pre><code class="language-sql">// 更新字符串属性
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});
</code></pre>
<h3 id="删除数据">删除数据</h3>
<p>删除数据是一个非常谨慎的操作,实际开发中不会物理删除数据,只是逻辑删除。方便数据恢复和大数据分析。这里只简单介绍。</p>
<ul>
<li>db.collection.remove() 删除集合中的一个或多个文档(默认删除多个)</li>
<li>db.collection.deleteOne() 删除集合中的一个文档</li>
<li>db.collection.deleteMany() 删除集合中的多个文档</li>
</ul>
<h2 id="springboot-mongodb-整合">SpringBoot MongoDB 整合</h2>
<p>如果你觉得Spring整合MongoDB略显麻烦,那SpringBoot整合MongoDB就是你的福音。SpringBoot旨在零配置,只需简单的两个步骤即可。<br>
第一步:在pom.xml文件中添加<code>spring-boot-starter-data-mongodb</code>。</p>
<pre><code class="language-xml">&lt;dependency&gt;        &lt;!-- 添加对mongodb的支持 --&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-mongodb&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>第二步:在application.properties文件中配置MongoDB数据库链接地址。<br>
链接MongoDB数据库地址规则:<code>spring.data.mongodb.uri=mongodb://account:password@ip:port/database</code><br>
其中 account和password方便是链接数据库的账号和密码。而database是需要链接的数据库地址</p>
<pre><code class="language-xml"># 没有账号密码可以简写
spring.data.mongodb.uri=mongodb://localhost:27017/itdragonstu
</code></pre>
<h2 id="spring-data-mongodb-编程">Spring Data MongoDB 编程</h2>
<p>Spring Data给我们提供了MongoTemplate类,极大的方便了我们的工作,但是若每个实体类都基于MongoTemplate重写一套CRUD的实现类,似乎显得有些笨重。于是我们可以将其简单的封装一下。步骤如下</p>
<p>第一步:创建用户实体类,其数据库表名就是类名首字母小写。<br>
第二步:封装MongoTemplate类,实现增删改查,分页,排序,主键自增等常用功能。<br>
第三步:创建封装类的Bean管理类,针对不同的实体类,需要配置不同的bean。<br>
第四步:创建测试类,测试:注册,更新,分页,排序,查询用户功能。</p>
<h3 id="创建用户实体类">创建用户实体类</h3>
<p>用户实体类有五个字段,除了主键ID,其他四个分别代表四个常用的类型(字符串,数字,对象,集合)。为了简化开发,实体类建议不实用@Document注解重命名User在MongoDB数据库中的表名。<br>
省略get/set方法和toString方法</p>
<pre><code class="language-java">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;
}
</code></pre>
<h3 id="封装mongotemplate类">封装MongoTemplate类</h3>
<p>SpringData提供的MongoTemplate类,极大的方便我们操作MongoDB数据库。可是它的很多方法都涉及到了Class,和CollectionName。针对不同的实体类,我们需要重复写不同的方法。这里,我们进一步封装,实现代码的高可用。<br>
实现的思路大致:将Class作为一个参数,在初始化MongoTemplate的封装类时赋值。这里有一个约束条件是:CollectionName是Class类名的首字母小写。</p>
<pre><code class="language-java">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&lt;String, Object&gt; 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&lt;String, Object&gt; 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;
        }
}
</code></pre>
<h3 id="创建封装类的bean管理类">创建封装类的Bean管理类</h3>
<p>这里用Bean注解修饰的方法名和测试类中ITDragonMongoHelper 的变量名要保持一致。这样才能具体知道是哪个实体类的数据操作。</p>
<pre><code class="language-java">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);
        }

}
</code></pre>
<h3 id="mongodb的测试类">MongoDB的测试类</h3>
<p>主要测试MongoDB保存数据,更新字符串,更新数值,更新对象(文档),更新集合,分页查询几个常用方法。</p>
<pre><code class="language-java">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 &lt; 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&lt;String&gt; ability = new ArrayList&lt;&gt;();
                        ability.add("Java");
                        user.setAbility(ability);
                        userMongoHelper.saveOrUpdate(user);
                        System.out.println("user : " + user.toString());
                }
        }
        @Test
        public void updateUserName() {
                System.out.println("^^^^^^^^^^^^^^^^^^^^^^updateUserName");
                Map&lt;String, Object&gt; updateMap = new HashMap&lt;&gt;();
                // 查询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&lt;String, Object&gt; updateMap = new HashMap&lt;&gt;();
                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&lt;String, Object&gt; updateMap = new HashMap&lt;&gt;();
                User user = (User) userMongoHelper.findOne(Criteria.where("name").is("itdragon-4"));
                if (null == user) {
                        System.out.println("^^^^^^^^^^^^^^^^^^^^^^User non-existent");;
                        return ;
                }
                ArrayList&lt;String&gt; 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&lt;User&gt; users = userMongoHelper.find(Criteria.where("age").gt(25), pageSize, pageNumber); // 查询age大于25的数据
                for (User user : users) {
                        System.out.println("user : " + user.toString());
                }
        }
}
</code></pre>
<h2 id="mongodb开发注意事项">MongoDB开发注意事项</h2>
<p>MongoDB对表结构要求不严,方便了我们的开发,同时也提高了犯错率,特别是公司来了新同事,这颗地雷随时都会爆炸。<br>
第一点: MongoDB通过key获取value的值。而这个value可以是内嵌的其他文档。因为没有主外键的概念,使用起来非常方便。若嵌套的文档太深,在更新数据是,需要注意不能覆盖原来的值。比如User表中的ability是一个集合,若传一个字符串,依然可以更新成功,但已经破坏了数据结构。这是很多新手容易犯的错。</p>
<p>第二点: 内嵌的文档属性名最好不要重名。举个例子,如果User表中的address对象,也有一个name的属性。那么在后续写代码的过程中,极容易混淆。导致数据更新异常。</p>
<p>第三点: 表的设计尽量做到扁平化,单表设计能有效提高数据库的查询销率。</p>
<p>第四点: 使用Mongoose约束数据结构,当数据结构不一致时操作失败。</p>
<p>前两点足以让一些老辈程序员抓狂,让新来的程序员懵圈。这也是很多开发人员喜欢又讨厌MongoDB的原因。</p>
<h2 id="总结">总结</h2>
<p>1 MongoDB是最接近关系型数据的非关系型数据库中的文档型数据库。</p>
<p>2 MongoDB支持非常丰富的查询语句,功能强大,但容易犯错。</p>
<p>3 MongoDB表结构的设计需谨慎,尽量减少嵌套层数,各嵌套的文档属性名尽量避免相同。</p>
<h2 id="参考文档">参考文档</h2>
<p>MongoDB官方文档: https://docs.mongodb.com</p>
<p>双刃剑MongoDB的学习和避坑到这里就结束了,感谢大家的阅读,欢迎点评。如果你觉得不错,可以"<strong>推荐</strong>"一下。也可以"<strong>关注</strong>"我,获得更多丰富的知识。</p>


</div>
<div id="MySignature" role="contentinfo">
    <!--
<img src="https://img2020.cnblogs.com/blog/806956/202007/806956-20200731234507722-1116809922.gif" title="扫码关注领福利">
-->
<div id="AllanboltSignature">
    <div id="PSignature" style="border: solid 1px #E5E5E5; padding: 10px 10px; font-family: 微软雅黑; font-size: 12px; background: #FFFEFE url(&quot;https://img2020.cnblogs.com/blog/806956/202007/806956-20200730162801810-951990406.jpg&quot;) no-repeat scroll 1% 50%; padding-left: 160px;">
      <span style="font-weight: bold">作者:ITDragon龙</span><br><br>
      <span style="font-weight: bold">出处:http://www.cnblogs.com/itdragon/</span><br><br>
        <span style="font-weight: bold">简介:每天都在进步,每周都在总结,架构师的修罗之路!你的一个<span style="font-size: 16px; color: #f60">点赞</span>,一句<span style="font-size: 16px; color: #f60">留言</span>,就可以让博主开心一笑,充满动力!ITDragon的微信公众号<span style="font-size: 16px; color: #f60">学英语会编程</span>只有英语学的好,源码才能看的爽</span><br><br>
      <span style="font-weight: bold">版权:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。</span>
      <br>
      
    </div>
</div><br><br>
来源:https://www.cnblogs.com/itdragon/p/8506278.html
頁: [1]
查看完整版本: 双刃剑MongoDB的学习和避坑