SpringBoot整合MongoDB

什么是MongoDB?

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供==可扩展的高性能数据存储解决方案==。 ​ MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson(数据类型)格式,因此可以存储比较复杂的数据类型。

MongoDB的特点

Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。(正则表达式支持索引)

它的特点是高性能、易部署、易使用,存储数据非常方便。主要功能特性有:

  1. 面向集合存储,易存储对象类型的数据。(集合相当于表)

  2. 模式自由。

  3. 支持动态查询。

  4. 支持完全索引,包含内部对象。

  5. 支持查询。

  6. 支持复制和故障恢复。(高可用)

  7. 使用高效的二进制数据存储,包括大型对象(如视频等)。

  8. 自动处理碎片,以支持云计算层次的扩展性。(mapreduce)

  9. 支持RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。

  10. 文件存储格式为BSON(一种JSON的扩展)。

MongoDB的体系结构

MongoDB 的逻辑结构是一种层次结构。主要由:文档(document)集合(collection)数据库(database)这三部分组成的。逻辑结构是面向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。

  1. MongoDB 的文档(document),相当于关系数据库中的一行记录。

  2. 多个文档组成一个集合(collection),相当于关系数据库的表。

  3. 多个集合(collection),逻辑上组织在一起,就是数据库(database)。

  4. 一个 MongoDB 实例支持多个数据库(database)。

文档(document)、集合(collection)、数据库(database)的层次结构如下图:

MongoDBMySQL
数据库(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);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值