MongoDB

MongoDB 集成, 操作

Posted by John-zero on March 13, 2018

资料集成

MongoDB 官网: https://www.mongodb.com

MongoDB DOCS: https://docs.mongodb.com

Spring Data MongoDB DOCS: https://docs.spring.io/spring-data/data-mongo/docs/


常规

MongoDB 访问: localhost:27017

MongoDB WEB 访问: localhost:28017

命令

	参考: 
		https://docs.mongodb.com/manual/reference/operator/
		https://docs.mongodb.com/manual/crud/
		...
	
	show dbs;
	
	use admin;
	
	show collections;
	
	// userMO 文档
	db.userMO.find();
	db.userMO.find().count();
	db.userMO.find({"_id" : ""}).count();
	
	...

Spring Boot 集成

pom.xml 引入依赖

	<dependency> 
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-mongodb</artifactId>
	</dependency> 
	
		或者
		
	<dependency>
		<groupId>org.springframework.data</groupId>
		<artifactId>spring-data-mongodb</artifactId>
	</dependency>
	
		参考: 
			Spring Data MongoDB: http://projects.spring.io/spring-data-mongodb/

application.properties 配置

	spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/database

模拟实体类

	import lombok.Data;

	@Data
	public class UserMO {
	
		public UserMO () {
		
		}
	
		public UserMO (String id, String name, int age, String phone) {
			this.id = id;
			this.name = name;
			this.age = age;
			this.phone = phone;
		}

		/**
		 * 主键
		 */
		private String id;
		/**
		 * 姓名
		 */
		private String name;
		/**
		 * 年龄
		 */
		private ing age;
		/**
		 * 手机号
		 */
		private String phone;
		/**
		 * 卡牌库
		 */
		private List<CardMO> cards = new ArrayList<>();
		
	}
	
	import lombok.Data;
	
	@Data
	public class CardMO {
	
		public CardMO () {
		
		}
	
		public CardMO (String id, String name, int number) {
			this.id = id;
			this.name = name;
			this.number = number;
		}

		/**
		 * 主键
		 */
		private String id;
		/**
		 * 卡牌名称
		 */
		private String name;
		/**
		 * 数量
		 */
		private ing number;
		
	}

基本操作
	
	@Autowired
	private MongoTemplate mongoTemplate;
	
	增加
		UserMO userMO = new UserMO("user-00", "贱贱", 27, "137 xxxx xxxx");
		
		userMO.getCards().add(new CardMO("card-cavalry-00", "骑兵", 1));
		userMO.getCards().add(new CardMO("card-infantry-00", "步兵", 1));
		userMO.getCards().add(new CardMO("card-archer-00", "弓箭手", 1));
		
		// save() 根据 _id 判断, 没有则插入, 有则覆盖
		mongoTemplate.save(userMO);
		
		// insert() 根据 _id 判断, 没有则插入, 有则会抛错
		mongoTemplate.insert(userMO);
		
		
	删除
		mongoTemplate.remove(new Query(Criteria.where("id").is(userId)), UserMO.class);
		
		删除位于 cards 集合里面的数据 (updateMulti 是 更新多条)
			Update update = new Update();
			update.pull("cards", new BasicDBObject("id", cardMO.getId()));
        mongoTemplate.updateMulti(new Query(Criteria.where("id").is(userId)), update, UserMO.class);
			
	更新
		Query query = new Query(Criteria.where("id").is(userMO.getId()));
		
		// 复杂版 (就是每一个属性都手动 set)
		Update update = new Update();
		update.set("name", "小贱贱");
		update.set("age", 28);
		
		// 简化版 (就是利用 Java 反射实现 set)
		Update update = MongoHelper.update(userMO);
		
		WriteResult writeResult = mongoTemplate.updateFirst(query, update, UserMO.class);
		
		更新 UserMO.cards 中的数据:
			Query query = new Query(Criteria.where("id").is(userMO.getId()).and("cards.id").is(card.getId()));
			Update update = new Update();
			update.set(String.format("cards.$.%s", "id"), "card-cavalry-00");
			update.set(String.format("cards.$.%s", "name"), "骑兵-00");
			WriteResult writeResult = mongoTemplate.updateFirst(query, update, CardMO.class);
			
		存在则更新, 不存在则增加
			Update update = new Update();
			update.addToSet("cards", cardMO);
			WriteResult _writeResult = mongoTemplate.upsert(new Query(Criteria.where("id").is(userMO.getId())), update, UserMO.class);
		
	查询
		查询一条
			UserMO userMO = mongoTemplate.findOne(new Query(Criteria.where("id").is(userId)), UserMO.class);
		查询列表
			List<UserMO> userMOs = mongoTemplate.find(new Query(Criteria.where("age").is(27)), UserMO.class);
		过滤属性不存在
			Criteria criteria = Criteria.where("id").is(userId);
			criteria.and("userId").is(new BasicDBObject("$exists", false));
		List<UserMO> userMOs = mongoTemplate.find(new Query(criteria), UserMO.class);
		
	分页, 排序
		Query query = new Query(criteria);
		int total = (int) mongoTemplate.count(query, UserMO.class);
		
		// desc, asc
		query.with(new Sort("desc", "id"));
		query.skip(0).limit(20);
		List<UserMO> userMOs = mongoTemplate.find(query, UserMO.class);

辅助小工具类

import com.fasterxml.jackson.annotation.JsonIgnore;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class MongoHelper {

	public static Map<String, Object> update (Object object) {
		Map<String, Object> update = new HashMap<>();

		try {
			Field [] fields = object.getClass().getDeclaredFields();
			for(Field field : fields) {
				// 属性忽略
				JsonIgnore jsonIgnore = field.getAnnotation(JsonIgnore.class);
				if(jsonIgnore != null) {
					continue;
				}

				boolean isAccessible = field.isAccessible();
				field.setAccessible(true);

				String fieldName = field.getName();
				Object fieldValue = field.get(object);
				if(fieldName != null && fieldValue != null) {
					update.put(fieldName, fieldValue);
				}

				field.setAccessible(isAccessible);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return update;
	}

}

命令 代表含义
$lt <
$lte <=
$gt >
$gte >=
$ne !=


使用经验

1. 树形结构 (应用于 菜单, 组织架构 等)
	Model Tree Structures: https://docs.mongodb.com/manual/applications/data-models-tree-structures/

	我的实现结构选择的是: 
		Model Tree Structures with Child References: https://docs.mongodb.com/manual/applications/data-models-tree-structures/
		
2. 金额 类型 BigDecimal

	注意: 
		2.1. 对于 BigDecimal 类型保存到 MongoDB 中是 字符串
		2.2. BigDecimal 没有 空构造函数
			AggregationResults<BigDecimal> aggregationResults = mongoTemplate.aggregate(aggregation, COLLECTION_NAME, BigDecimal.class);
			这样会提示:
				org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate java.math.BigDecimal using constructor NO_CONSTRUCTOR with arguments
		
3. 过滤条件限制某个属性不存在
	criteria.and("userId").is(new BasicDBObject("$exists", false));
	
4. 日期类型
	if (condition.getCreatedTimeFrom() != null && condition.getCreatedTimeTo() != null) {
		criteria.and("createdTime").gte(condition.getCreatedTimeFrom()).lt(condition.getCreatedTimeTo());
	} else if (condition.getCreatedTimeFrom() != null) {
		criteria.and("createdTime").gte(condition.getCreatedTimeFrom());
	} else if (condition.getCreatedTimeTo() != null) {
		criteria.and("createdTime").lt(condition.getCreatedTimeTo());
	}

踩坑经验

1. 根据 CardMO 的 主键 ID 删除 UserMO 实体对象中 cards 中的 CardMO 数据
	MongoDB 文档: https://docs.mongodb.com/manual/reference/operator/update/pull/
	使用 Spring Data MongoDB 操作:
		Query query = new Query(Criteria.where("id").is("1100004083497709568"));
		Update update = new Update().pull("cards", new BasicDBObject("id", "1100487509678071808"));
		WriteResult writeResult = mongoTemplate.updateMulti(query, update, UserMO.class);

2. 并发问题一定要注意
	当涉及消费MQ消息操作MongoDB的时候, 一定要注意并发问题, 可以加分布式锁