什么是MongoDB?
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供==可扩展的高性能数据存储解决方案==。 MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson(数据类型)格式,因此可以存储比较复杂的数据类型。
MongoDB的特点
Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。(正则表达式支持索引)
它的特点是高性能、易部署、易使用,存储数据非常方便。主要功能特性有:
-
面向集合存储,易存储对象类型的数据。(集合相当于表)
-
模式自由。
-
支持动态查询。
-
支持完全索引,包含内部对象。
-
支持查询。
-
支持复制和故障恢复。(高可用)
-
使用高效的二进制数据存储,包括大型对象(如视频等)。
-
自动处理碎片,以支持云计算层次的扩展性。(mapreduce)
-
支持RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。
-
文件存储格式为BSON(一种JSON的扩展)。
MongoDB的体系结构
MongoDB 的逻辑结构是一种层次结构。主要由:文档(document)、集合(collection)、数据库(database)这三部分组成的。逻辑结构是面向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。
-
MongoDB 的文档(document),相当于关系数据库中的一行记录。
-
多个文档组成一个集合(collection),相当于关系数据库的表。
-
多个集合(collection),逻辑上组织在一起,就是数据库(database)。
-
一个 MongoDB 实例支持多个数据库(database)。
文档(document)、集合(collection)、数据库(database)的层次结构如下图:
MongoDB | MySQL |
数据库(databases) | 数据库(databases) |
集合(collections) | 表(table) |
文档(document) | 行(row) |
MongoDB的数据类型
数据类型 | 描述 |
string | 字符串,存储数据常用的数据类型,在MongoDB中,utf-8编码的字符串才是合法的 |
integer | 整型数值,用于存储数值,根据所采用的服务器,分为32为和64位 |
Boolean | 布尔型,用于存储布尔值(真/假) |
double | 双精度浮点值 |
array | 用于将数组或列表或多个值存储为一个键 |
timestamp | 时间戳,用于记录文档的修改或添加的具体时间 |
object | 用于内嵌文档 |
null | 用于创建空值 |
date | 日期时间,用Unix时间格式类存储当前日期或时间, |
objectId | 对象id,用于创建文档的ID |
binary data | 二进制数据,用于存储二进制数据 |
code | 代码类型,用于在文档中存储JavaScript代码 |
regular expression | 用于存储正则表达式 |
特殊说明:
- ObjectId
- ObjectId类型唯一主键,可以很快的生成和排序,包含12byte
- 时间戳
- BSON有一个特殊的时间戳,与普通的日期不相关,时间戳值是一个64位的值
- 前32位是一个time_t的值【与Unix新纪元(1970年1月1日)相差的秒数】
- 后32位是在某秒中操作的一个递增的序数
- 在单个Mongo实例中,时间戳通常是唯一的
MongoDB和Redis的比较
比较指标 | MongoDB(海量数据) | Redis(热点数据) | 比较说明 |
---|---|---|---|
实现语言 | c++ | c/c++ | - |
协议 | ==BSON,自定义二进制== | ==类telnet(TCP/IP)== | - |
性能 | ==依赖内存 (内存映射文件技术)== | ==依赖内存==(纯内存) | Redis优于MongoDB |
可操作性 | 丰富的数据表达,索引;最类似于关系型数据库,支持丰富的查询语句 | 数据类型丰富,较少的IO | - |
内存及存储 | 适合大数据量存储,依赖系统虚拟内存,采用镜像文件存储;内存占用率比较高,官方建议独立部署在64位系统 | Redis2.0后支持虚拟内存特性(VM) 突破物理内存限制;数据可以设置时效性,类似于memcache | 不同的应用场景,各有千秋 |
可用性 | 支持master-slave,replicatset(内部采用paxos选举算法,自动故障恢复),auto sharding机制,对客户端屏蔽了故障转移和切片机制 | 依赖客户端来实现分布式读写;主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制;不支持auto sharding,需要依赖程序设定一致性hash机制 | MongoDB优于Redis;单点问题上,MongoDB应用简单,相对用户透明,Redis比较复杂,需要客户端主动解决.(MongoDB一般使用replicasets和sharding相结合,replicasets侧重高可用性以及高可靠,sharding侧重性能,水平扩展) |
可靠性 | 从1.8版本后,采用binlog方式(类似Mysql) 支持持久化 | 依赖快照进行持久化;AOF增强可靠性;增强性的同时,影响访问性能 | mongodb在启动时,专门初始化一个线程不断循环(除非应用crash掉),用于在一定时间周期内来从defer队列中获取要持久化的数据并写入到磁盘的journal(日志)和mongofile(数据)处,当然它不是在用户添加记录时就写到磁盘上 |
一致性 | ==以前版本不支持事务,靠客户端保证== ==最新4.x的支持事务== | ==支持事务,比较脆,仅能保证事务中的操作按顺序执行== | - |
数据分析 | 内置数据分析功能(mapreduce) | 不支持 | MongoDB优于Redis |
应用场景 | ==海量数据存储和访问效率提升== | ==热点数据的存储== | - |
SpringDataMongoDB
- 创建工程,导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
- 创建application.yml文件,添加配置信息
spring:
data:
mongodb:
uri: mongodb://admin:123456@127.0.0.1:27017
database: commentdb
- 创建与MongoDB表中关联的实体类
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
import java.util.Date;
@Document(collection = "comment")
@Data
public class Comment implements Serializable {
private static final long serialVersionUID = 7277281649066797500L;
@Id
private String _id;
private String articleid;
private String content;
private String userid;
private String parentid;
private Date publishdate;
private Integer thumbup;
private String username;
}
- 使用IdWorker生成主键
package com.zhuang.utils;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
/**
* <p>名称:IdWorker.java</p>
* <p>描述:分布式自增长ID</p>
* <pre>
* Twitter的 Snowflake JAVA实现方案
* </pre>
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
* 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
* 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
* 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
* 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
* 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
* 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
* <p>
* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
* @author Polim
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
}
- 创建启动类
import com.zhuang.utils.IdWorker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class MongoApplication {
public static void main(String[] args) {
SpringApplication.run(MongoApplication.class, args);
}
@Bean
public IdWorker idWorker() {
return new IdWorker(1, 1);
}
}
- 使用MongoTemplate进行简单的CURD测试
package com.zhuang;
import com.zhuang.pojo.Comment;
import com.zhuang.utils.IdWorker;
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.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
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.test.context.junit4.SpringRunner;
import java.util.Date;
import java.util.List;
import java.util.Map;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MongoTest {
@Autowired
private IdWorker idWorker;
@Autowired
private MongoTemplate mongoTemplate;
/**
* 测试添加
*/
@Test
public void add() {
Comment comment = new Comment();
comment.set_id(idWorker.nextId() + "");
comment.setContent("哈哈哈哈");
comment.setUsername("测试姓名");
comment.setUserid("001");
comment.setPublishdate(new Date());
comment.setThumbup(66666);
mongoTemplate.save(comment);
}
/**
* 根据主键删除
*/
@Test
public void deleteById() {
mongoTemplate.remove(Query.query(Criteria.where("_id").is("1447071723357868032")), Comment.class);
}
/**
* 修改数据
*/
@Test
public void update() {
Query query = new Query();
query.addCriteria(Criteria.where("_id").is("1447071723357868032"));
Update update = new Update();
update.set("thumbup", 100000);
mongoTemplate.updateFirst(query, update, Comment.class);
}
/**
* 查询所有
*/
@Test
public void findAll() {
List<Comment> comments = mongoTemplate.findAll(Comment.class);
comments.forEach(System.out::println);
}
/**
* 根据ID查询
*/
@Test
public void findById() {
Comment comment = mongoTemplate.findById("1447075678179692544", Comment.class);
System.out.println(comment);
}
/**
* 获取当前点赞数大于200并统计用户的总点赞数
*/
@Test
public void findTotalThumbupByUserId() {
Aggregation aggregate = new TypedAggregation<Comment>(Comment.class, Aggregation.match(Criteria.where("thumbup").gt(200)), Aggregation.group("userid").sum("thumbup").as("sum"));
AggregationResults<Map> results = mongoTemplate.aggregate(aggregate, mongoTemplate.getCollectionName(Comment.class), Map.class);
List<Map> mappedResults = results.getMappedResults();
System.out.println(mappedResults);
}
/**
* 按照点赞数排序,查询前3条评论
*/
@Test
public void findByPage() {
int page = 2;
int pageSize = 3;
Query query = new Query();
query.with(Sort.by(Sort.Order.desc("thumbup")));
query.skip((page - 1) * pageSize).limit(pageSize);
List<Comment> comments = mongoTemplate.find(query, Comment.class);
comments.forEach(System.out::println);
}
}