实战:五

4.16

实现数据字典

后端接口部分

/**
 * 为了实现数据字典,这个方法用来查找对应父 id 的子 id
 *
 * @param id
 * @return
 */
@ApiOperation("根据数据 id 查询子数据列表")
@GetMapping("findChildData/{id}")
public Result findChildData(@PathVariable Long id) {
    List<Dict> childData = dictService.findChildData(id);
    return Result.ok(childData);
}
//service 层部分。用于判断该数据是否还有子节点,是否还需要继续下沿。
/**
 * 根据传进的 id,查询 parent_id 为此 id 的所有数据。
 * @param id
 * @return
 */
@Override
public List<Dict> findChildData(Long id) {
    QueryWrapper<Dict> dictQueryWrapper = new QueryWrapper<>();
    dictQueryWrapper.eq("parent_id",id);
    List<Dict> dicts = baseMapper.selectList(dictQueryWrapper);
    //list 集合中每个数据判断是否还有子节点
    for (Dict dict : dicts) {
        // 根据当前字段的 id 判断他有没有子节点。
        // 没有 ->false; 有 ->true
        dict.setHasChildren(isHasChildren(dict.getId()));
    }
    return dicts;
}

/**

  • 用来判断数据是否有子节点。
  • @param id
  • @return
  • /
    private boolean isHasChildren(Long id){
    QueryWrapper<Dict> dictQueryWrapper = new QueryWrapper<>();
    dictQueryWrapper.eq("parent_id",id);
    Integer integer = baseMapper.selectCount(dictQueryWrapper);
    return integer>0;
    }

前端部分

首先定义 router

{
    path: '/cmn',
    component: Layout,
    redirect: '/cmn/list',
    name: '数据管理',
    meta: { title: '数据管理', icon: 'example' },
    alwaysShow:true,// 如果只有一个子模块也会单独列出。flase 时不会。
    children: [
      {
        path: 'list',
        name: '数据字典',
        component: () => import('@/views/dict/list'),
        meta: { title: '数据字典', icon: 'table' }
      },
    ]
  },

api/index定义接口信息

import request from '@/utils/request'
export default {
  // 数据列表方法
  dictList(id){
    return request({
      url:`/admin/cmn/dict/findChildData/${id}`,
      method:"get",
    })
  }
}

页面渲染

基于 Element-ui 完成的页面渲染。

<div>
    <el-table
      :data="list"
      style="width: 100%"
      row-key="id"
      border
      lazy
      :load="getChildrens"
      :tree-props="{children:'children', hasChildren:'hasChildren'}"
    >
      <el-table-column label="名称" width="230" align="left">
        <template slot-scope="scope">
          <span>{{ scope.row.name }}</span>
        </template>
      </el-table-column>
  <span class="hljs-tag">&lt;<span class="hljs-name">el-table-column</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"编码"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"220"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">slot-scope</span>=<span class="hljs-string">"{ row }"</span>&gt;</span>
      {{ row.dictCode }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">el-table-column</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">el-table-column</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"值"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"230"</span> <span class="hljs-attr">align</span>=<span class="hljs-string">"left"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">slot-scope</span>=<span class="hljs-string">"scope"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{{ scope.row.value }}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">el-table-column</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">el-table-column</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"创建时间"</span> <span class="hljs-attr">align</span>=<span class="hljs-string">"center"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">slot-scope</span>=<span class="hljs-string">"scope"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{{ scope.row.createTime }}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">el-table-column</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">el-table</span>&gt;</span>

</div>

methods: {
    getDictList: function (id) {
        dict.dictList(id).then((response) => {
            this.list = response.data;
        });
    },
        getChildrens:function(tree, treeNode, resolve) {
            dict.dictList(tree.id).then((response) => {
                resolve(response.data);
            });
        },
},
    // 生命周期 - 创建完成(可以访问当前 this 实例)
    created() {
        this.getDictList(1);
    },

EasyExcel

Java 解析、生成 Excel 比较有名的框架有 Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi 有一套 SAX 模式的 API 可以一定程度的解决一些内存溢出的问题,但 POI 还是有一些缺陷,比如 07 版 Excel 解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel 重写了 poi 对 07 版 Excel 的解析,能够原本一个 3M 的 excel 用 POI sax 依然需要 100M 左右内存降低到几 M,并且再大的 excel 不会出现内存溢出,03 版依赖 POI 的 sax 模式。在上层做了模型转换的封装,让使用者更加简单方便。

EasyExcel 是一个基于 Java 的简单、省内存的读写 Excel 的开源项目。在尽可能节约内存的情况下支持读写百 M 的 Excel。

  • 引入依赖

    <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.1.1</version>
    </dependency>
    
  • 需要一个实体类

    @Data
    // 不能存在有参构造方法,不会读取时会报错。
    public class UserData {
        @ExcelProperty(value = "用户编号",index = 0)
        private int uid;
        @ExcelProperty(value = "用户名称",index = 1)
        private String uname;
    }
    
  • 写操作逻辑

    public static void main(String[] args) {
        // 设置文件地址和文件名
        String filename="G:\\excel\\01.xlsx";
        // 构建一个数据的 list 集合
        ArrayList<UserData> userData = new ArrayList<UserData>();
        for (int i = 0; i < 10; i++) {
            UserData userData1 = new UserData(i, "hello" + i);
            userData.add(userData1);
        }
        // 需要指定地址,数据类型,表别名,数据。
        EasyExcel.write(filename,UserData.class).sheet("用户信息")
            .doWrite(userData);
    }
    
  • 读操作逻辑

    需要一个监督类

    public class ExcelListen extends AnalysisEventListener<UserData> {
        /**
         * 一行一行读取内容
         */
        @Override
        public void invoke(UserData userData, AnalysisContext analysisContext) {
            System.out.println(userData);
        }
    
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">invokeHead</span><span class="hljs-params">(Map&lt;Integer, CellData&gt; headMap, AnalysisContext context)</span> {
    
        System.out.println(<span class="hljs-string">"表头信息:"</span>+headMap);
    }
    
    <span class="hljs-comment">/**
     * 读取后执行的方法
     * <span class="hljs-doctag">@param</span> analysisContext
     */</span>
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doAfterAllAnalysed</span><span class="hljs-params">(AnalysisContext analysisContext)</span> {
    
    }
    

    }

    读取的逻辑

    public class TestRead {
        public static void main(String[] args) {
            // 设置读取文件的位置。
            String filename = "G:\\excel\\01.xlsx";
            // 调用方法, 参数:文件地址、文件类型、监听器。
            EasyExcel.read(filename,UserData.class, new ExcelListen()).sheet().doRead();
        }
    }
    

上传和下载文件

下载

@Override
public void export(HttpServletResponse response) {
    // 设置下载的信息
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");
    // 这里 URLEncoder.encode 可以防止中文乱码 当然和 easyexcel 没有关系
    String fileName = "dict";
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
    // 查询数据库
    List<Dict> dictList = baseMapper.selectList(null);
    ArrayList<DictEeVo> arrayList = new ArrayList();
    // 将 dict 的 id 复制给 dicteevo
    for (Dict dict : dictList) {
        DictEeVo dictEeVo = new DictEeVo();
        BeanUtils.copyProperties(dict,dictEeVo);
        arrayList.add(dictEeVo);
    }
<span class="hljs-comment">//Excel写</span>
<span class="hljs-keyword">try</span> {
    EasyExcel.write(response.getOutputStream(), DictEeVo.class)
        .sheet(<span class="hljs-string">"dict"</span>)
        .doWrite(arrayList);
} <span class="hljs-keyword">catch</span> (IOException e) {
    e.printStackTrace();
}

}

上传

//controller
/**
 * 用于向服务器端提交内容
 */
@ApiOperation("上传数据字典")
@PostMapping("importData")
public Result importData(MultipartFile file) {// 通过 MultipartFile 来接收文件
    dictService.importData(file);
    return Result.ok();
}
//service
/**
 * 将上传的 excel 写入数据库
 * @param file
 */
@Override
public void importData(MultipartFile file) {
    try {
        // 使用 EasyExcel 的 read 方法一行一行的读
        EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(baseMapper)).sheet().doRead();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
// 监听器
public class DictListener extends AnalysisEventListener<DictEeVo> {
<span class="hljs-keyword">private</span> DictMapper dictMapper;

<span class="hljs-keyword">public</span> <span class="hljs-title function_">DictListener</span><span class="hljs-params">(DictMapper dictMapper)</span> {
    <span class="hljs-built_in">this</span>.dictMapper = dictMapper;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">invoke</span><span class="hljs-params">(DictEeVo dictEeVo, AnalysisContext analysisContext)</span> {
    <span class="hljs-comment">//将DictEeVo转换为Dict对象</span>
    <span class="hljs-type">Dict</span> <span class="hljs-variable">dict</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Dict</span>();
    BeanUtils.copyProperties(dictEeVo,dict);
    <span class="hljs-comment">//调用数据库方法</span>
    dictMapper.insert(dict);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doAfterAllAnalysed</span><span class="hljs-params">(AnalysisContext analysisContext)</span> {

}

}

前端

<div class="el-toolbar">
    <div class="el-toolbar-body" style="justify-content: flex-start">
        <el-button type="text" @click="exportData"
                   ><i class="fa fa-plus" /> 导出</el-button
            >
        <el-button type="text" @click="importData"
                   ><i class="fa fa-plus" /> 导入</el-button
            >
    </div>
<span class="hljs-tag">&lt;<span class="hljs-name">el-dialog</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"导入"</span> <span class="hljs-attr">:visible.sync</span>=<span class="hljs-string">"dialogImportVisible"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"480px"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">el-form</span> <span class="hljs-attr">label-position</span>=<span class="hljs-string">"right"</span> <span class="hljs-attr">label-width</span>=<span class="hljs-string">"170px"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">el-form-item</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"文件"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">el-upload</span>
                       <span class="hljs-attr">:multiple</span>=<span class="hljs-string">"false"</span>
                       <span class="hljs-attr">:on-success</span>=<span class="hljs-string">"onUploadSuccess"</span>
                       <span class="hljs-attr">:action</span>=<span class="hljs-string">"'http://localhost:8202/admin/cmn/dict/importData'"</span>
                       <span class="hljs-attr">class</span>=<span class="hljs-string">"upload-demo"</span>
                       &gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">el-button</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"small"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"primary"</span>&gt;</span>点击上传<span class="hljs-tag">&lt;/<span class="hljs-name">el-button</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"tip"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"el-upload__tip"</span>&gt;</span>
                    只能上传exls文件,且不超过500kb
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">el-upload</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">el-form-item</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">el-form</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"footer"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"dialog-footer"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">el-button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"dialogImportVisible = false"</span>&gt;</span> 取消 <span class="hljs-tag">&lt;/<span class="hljs-name">el-button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">el-dialog</span>&gt;</span>

    // 上传文件(导入数据)的方法
    importData: function () {
        this.dialogImportVisible=true;
    },
    // 上传成功的方法
    onUploadSuccess:function(){
        // 关闭弹框
        this.dialogImportVisible=false;
        // 刷新页面
        this.getDictList(1);
    }

为数据字典添加缓存

Spring Cache 是一个非常优秀的缓存组件。自 Spring 3.1 起,提供了类似于 @Transactional 注解事务的注解 Cache 支持,且提供了 Cache 抽象,方便切换各种底层 Cache(如:redis)

  • 导入依赖

    <!-- redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    

    <!-- spring2.X 集成 redis 所需 common-pool2-->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
    </dependency>

  • 编写配置类

    @Configuration
    @EnableCaching
    public class RedisConfig {
        /**
         * 自定义 key 规则
         *
         * @return
         */
        @Bean
        public KeyGenerator keyGenerator() {
            return (target, method, params) -> {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            };
        }
    
    <span class="hljs-comment">/**
     * 设置RedisTemplate规则
     *
     * <span class="hljs-doctag">@param</span> redisConnectionFactory
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-keyword">public</span> RedisTemplate&lt;Object, Object&gt; <span class="hljs-title function_">redisTemplate</span><span class="hljs-params">(RedisConnectionFactory redisConnectionFactory)</span> {
        RedisTemplate&lt;Object, Object&gt; redisTemplate = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RedisTemplate</span>&lt;&gt;();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        <span class="hljs-type">Jackson2JsonRedisSerializer</span> <span class="hljs-variable">jackson2JsonRedisSerializer</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Jackson2JsonRedisSerializer</span>(Object.class);
    
        <span class="hljs-comment">//解决查询缓存转换异常的问题</span>
        <span class="hljs-type">ObjectMapper</span> <span class="hljs-variable">om</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ObjectMapper</span>();
        <span class="hljs-comment">// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public</span>
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        <span class="hljs-comment">// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常</span>
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
    
        <span class="hljs-comment">//序列号key value</span>
        redisTemplate.setKeySerializer(<span class="hljs-keyword">new</span> <span class="hljs-title class_">StringRedisSerializer</span>());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(<span class="hljs-keyword">new</span> <span class="hljs-title class_">StringRedisSerializer</span>());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
    
        redisTemplate.afterPropertiesSet();
        <span class="hljs-keyword">return</span> redisTemplate;
    }
    
    <span class="hljs-comment">/**
     * 设置CacheManager缓存规则
     *
     * <span class="hljs-doctag">@param</span> factory
     * <span class="hljs-doctag">@return</span>
     */</span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-keyword">public</span> CacheManager <span class="hljs-title function_">cacheManager</span><span class="hljs-params">(RedisConnectionFactory factory)</span> {
        RedisSerializer&lt;String&gt; redisSerializer = <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringRedisSerializer</span>();
        <span class="hljs-type">Jackson2JsonRedisSerializer</span> <span class="hljs-variable">jackson2JsonRedisSerializer</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Jackson2JsonRedisSerializer</span>(Object.class);
    
        <span class="hljs-comment">//解决查询缓存转换异常的问题</span>
        <span class="hljs-type">ObjectMapper</span> <span class="hljs-variable">om</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ObjectMapper</span>();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
    
        <span class="hljs-comment">// 配置序列化(解决乱码的问题),过期时间600秒</span>
        <span class="hljs-type">RedisCacheConfiguration</span> <span class="hljs-variable">config</span> <span class="hljs-operator">=</span> RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofSeconds(<span class="hljs-number">600</span>))
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
            .disableCachingNullValues();
    
        <span class="hljs-type">RedisCacheManager</span> <span class="hljs-variable">cacheManager</span> <span class="hljs-operator">=</span> RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
        <span class="hljs-keyword">return</span> cacheManager;
    }
    

    }

    • 使用 Spring Cache 的注解完成缓存

      注解名 描述 参数
      @Cacheable 根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。 value:缓存名,必填,它指定了你的缓存存放在哪块命名空间。cacheNames:与 value 差不多,二选一即可。key:可选属性,可以使用 SpEL 标签自定义缓存的 key 。
      @CachePut 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。 value:缓存名,必填,它指定了你的缓存存放在哪块命名空间。cacheNames:与 value 差不多,二选一即可。key:可选属性,可以使用 SpEL 标签自定义缓存的 key 。
      @CacheEvict 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上 value:缓存名,必填,它指定了你的缓存存放在哪块命名空间。cacheNames:与 value 差不多,二选一即可。key:可选属性,可以使用 SpEL 标签自定义缓存的 key。allEntries:是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存。 beforeInvocation:是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存。
    • 修改 service 层方法

      @Override
      @Cacheable(value = "dict",keyGenerator = "keyGenerator")// 添加上此注解。
      public List<Dict> findChildData(Long id) {
          QueryWrapper<Dict> dictQueryWrapper = new QueryWrapper<>();
          dictQueryWrapper.eq("parent_id", id);
          List<Dict> dicts = baseMapper.selectList(dictQueryWrapper);
          //list 集合中每个数据判断是否还有子节点
          for (Dict dict : dicts) {
              // 根据当前字段的 id 判断他有没有子节点。
              // 没有 ->false; 有 ->true
              dict.setHasChildren(isHasChildren(dict.getId()));
          }
          return dicts;
      }
      

    MongDb

    NoSQL(NoSQL = Not Only SQL),意即反 SQL 运动,指的是非关系型的数据库,是一项全新的数据库革命性运动,早期就有人提出,发展至 2009 年趋势越发高涨。NoSQL 的拥护者们提倡运用非关系型的数据存储,相对于目前铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。

    为什幺使用 NoSQL :

    • 对数据库高并发读写。
    • 对海量数据的高效率存储和访问。
    • 对数据库的高可扩展性和高可用性。

    弱点:

    • 数据库事务一致性需求
    • 数据库的写实时性和读实时性需求
    • 对复杂的 SQL 查询,特别是多表关联查询的需求

    什么是 MongoDB

    MongoDB 是由 C++ 语言编写的,是一个基于分布式文件存储的开源数据库系统。

    在高负载的情况下,添加更多的节点,可以保证服务器性能。

    MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

    MongoDB 将数据存储为一个文档,数据结构由键值 (key=>value) 对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

    安装 MongDB

    • 拉取镜像

      docker pull mongo:latest
      
    • 创建并启动容器

      docker run -d --restart=always -p 27017:27017 --name mymongo -v /data/db:/data/db -d mongo
      

      p67


4.17

查漏补缺:MongoDB

传统的关系型数据库(如 MySQL),在数据操作的“三高”需求以及应对 Web2.0 的网站需求面前,显得力不从心。

解释:“三高”需求:

  • High performance - 对数据库高并发读写的需求。

  • Huge Storage - 对海量数据的高效率存储和访问的需求。

  • High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求。

而 MongoDB 可应对“三高”需求。

具体的应用场景如:

1)社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。

2)游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。

3)物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将

订单所有的变更读取出来。

4)物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。

5)视频直播,使用 MongoDB 存储用户信息、点赞互动信息等。

这些应用场景中,数据操作方面的共同特点是:

(1)数据量大

(2)写入操作频繁(读写都很频繁)

(3)价值较低的数据,对事务性要求不高

对于这样的数据,我们更适合使用 MongoDB 来实现数据的存储。

什么时候选择 MongoDB

  • 在架构选型上,除了上述的三个特点外,如果你还犹豫是否要选择它?可以考虑以下的一些问题:

  • 应用不需要事务及复杂 join 支持

  • 新应用,需求会变,数据模型无法确定,想快速迭代开发

  • 应用需要 2000-3000 以上的读写 QPS(更高也可以)

  • 应用需要 TB 甚至 PB 级别数据存储

  • 应用发展迅速,需要能快速水平扩展

  • 应用要求存储的数据不丢失

  • 应用需要 99.999% 高可用

  • 应用需要大量的地理位置查询、文本查询

如果上述有 1 个符合,可以考虑 MongoDB,2 个及以上的符合,选择 MongoDB 绝不会后悔。


MongoDB 简介

MongoDB 是一个开源、高性能、无模式的文档型数据库,当初的设计就是用于简化开发和方便扩展,是 NoSQL 数据库产品中的一种。是最像关系型数据库(MySQL)的非关系型数据库。

它支持的数据结构非常松散,是一种类似于 JSON 的 格式叫 BSON,所以它既可以存储比较复杂的数据类型,又相当的灵活。

MongoDB 中的记录是一个文档,它是一个由字段和值对(fifield:value)组成的数据结构。MongoDB 文档类似于 JSON 对象,即一个文档认为就是一个对象。字段的数据类型是字符型,它的值除了使用基本的一些类型外,还可以包括其他文档、普通数组和文档数组。

image-20220417205804568

image-20220417205841262


MongoDB 的特点

(1)高性能

MongoDB 提供高性能的数据持久性。特别是,

对嵌入式数据模型的支持减少了数据库系统上的 I/O 活动。

索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。(文本索引解决搜索的需求、TTL 索引解决历史数据自动过期的需求、地

理位置索引可用于构建各种 O2O 应用)

mmapv1、wiredtiger、mongorocks(rocksdb)、in-memory 等多引擎支持满足各种场景需求。

Gridfs 解决文件存储的需求。

(2)高可用性:

MongoDB 的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。

(3)高扩展性:

MongoDB 提供了水平可扩展性作为其核心功能的一部分。

分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展)

从 3.4 开始,MongoDB 支持基于片键创建数据区域。在一个平衡的集群中,MongoDB 将一个区域所覆盖的读写只定向到该区域内的那些

片。

(4)丰富的查询支持:

MongoDB 支持丰富的查询语言,支持读和写操作 (CRUD),比如数据聚合、文本搜索和地理空间查询等。

(5)其他特点:如无模式(动态模式)、灵活的文档模型


MongoDB 安装

#拉取镜像 
docker pull mongo:latest
#创建和启动容器 
docker run -d --restart=always -p 27017:27017 --name mymongo -v /data/db:/data/db -d mongo
#进入容器 
docker exec -it mymongo /bin/bash
#使用 MongoDB 客户端进行操作 
mongo 


MongoDB 基本命令

#Help 查看命令提示 
db.help()
#切换 / 创建数据库
use test#如果数据库不存在,则创建数据库,否则切换到指定数据库
#查询所有数据库 
show dbs;
#删除当前使用数据库 
db.dropDatabase();
#查看当前使用的数据库 
db.getName();
#显示当前 db 状态 
db.stats();
#当前 db 版本 
db.version();
#查看当前 db 的链接机器地址 
db.getMongo〇;

文档

文档是一组键值 (key-value) 对(即 BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。

下表列出了 RDBMS 与 MongoDB 对应的术语:

RDBMS MongoDB
数据库 数据库
表格 集合
文档
字段
表联合 嵌入文档
主键 主键 (MongoDB 提供了 key 为 _id)

文档的一些操作

#创建一个集合(table)
db.createCollection( "collName");
#得到指定名称的集合(table )
db.getCollection("user");

MongoDB 的 CRUD 操作

  • INSERT

    > db.User.save({name:'zhangsan',age:21,sex:true})
    > db.User.find()
    {"_id": Objectld("4f69e680c9106ee2ec95da66"), "name": "zhangsan", "age": 21,
    "sex": true}
    

    _id 组合

    Objectld 是、id”的默认类型。Objectld 使用 12 字节的存储空间,每个字节二位十六进制数字, 是一个 24 位的字符串

    ![img](file:///C:/Users/Boerk/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)

  • Query

    # select * from User where name = 'zhangsan' sql
    > db.User.find({name:"zhangsan"}) #MongoDB
    

    # select name, age from User where age = 21
    > db.User.find({age:21}, {'name':1, 'age':1})

    #在 MongoDB 中使用 sort()方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。
    # select * from User order by age
    > db.User.find().sort({age:1})

    #SUCE
    #在 MongoDB 中使用 limit()方法来读取指定数量的数据,skip() 方法来跳过指定数量的数据
    # select * from User skip 2 limit 3
    > db.User.find().skip(0).limit(3)

    # select * from User where age in (21, 26, 32)
    > db.User.find({age:{$in:[21,26,32]}})

    # select count(*) from User where age >20
    > db.User.find({age:{$gt:20}}).count()

    # select * from User where age = 21 or age = 28
    > db.User.find({$or:[{age:21}, {age:28}]})

  • Update

    # update Userset age = 100, sex = 0 where name = 'user1'
    > db.User.update({name:"zhangsan"}, {$set:{age:100, sex:0}})
    

    Update() 有几个参数需要注意。

    db.collection.update(criteria, objNew, upsert, mult)

    criteria: 需要更新的条件表达式

    objNew: 更新表达式

    upsert: 如 FI 标记录不存在,是否插入新文档。

    multi: 是否更新多个文档。

  • Remove

    #移除对应 id 的行
    > db.User.remove(id)
    #移除所有
    > db.User.remove({})
    

Springboot 整合 MongoDB

spring-data-mongodb 提供了 MongoTemplate 与 MongoRepository 两种方式访问 mongodb,MongoRepository 操作简单,MongoTemplate 操作灵活,我们在项目中可以灵活适用这两种方式操作 mongodb,MongoRepository 的缺点是不够灵活,MongoTemplate 正好可以弥补不足。

  • 引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    
  • 配置 MongoDB 的地址

    spring.data.mongodb.uri=mongodb://ip:27017/test
    
  • 创建对应的实体类

    @Data
    @Document("User")
    public class User {
        @Id
        private String id;
        private String name;
        private Integer age;
        private String email;
        private String createDate;
    }
    
  • 添加

    /**
     * 这是对 MongoDB 进行新增的方法
     */
    @Test
    public void add(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        user.setEmail("110120119");
        User user1 = mongoTemplate.insert(user);
        System.out.println(user1);
    }
    
  • 查询

    /**
     * 这是对 MongoDB 进行查找的方法
     */
    @Test
    public void query() {
        List<User> all = mongoTemplate.findAll(User.class);
        System.err.println(all);
    }
    

    /**

    • 这是根据id对MongoDb的精细查询的方法
      */
      @Test
      public void queryById(){
      User user = mongoTemplate.findById("625c1bbaa2f93370884eafeb", User.class);
      System.out.println(user);
      }
// 条件查询
/**
 * 这是根据条件查询的方法
 */
@Test
public void queryUserList() {
    //name=zhangsan &age=18
    Query query = new Query(Criteria
                            .where("name").is("zhangsan") //name is zhangsan
                            .and("age").is(18)            //age is 18
                           );
    mongoTemplate.find(query, User.class);
}
// 模糊查询
/**
 * 模糊查询
 */
@Test
public void queryUserLike(){
    // 名字中包含 zhang
    String name = "zhang";
    // 正则表达式的匹配规则
    String regex = String.format("%s%s%s", "^.*", name, ".*$");
    Pattern pattern = Pattern.compile(regex,Pattern.CASE_INSENSITIVE);
    // 匹配正则
    Query query = new Query(Criteria.where("name").regex(pattern));
    List<User> users = mongoTemplate.find(query, User.class);
    System.err.println(users);
}
// 分页查询
/**
 * 分页查询
 */
@Test
public void queryUserLikePage(){
    // 分页信息
    int pageNum=1;
    int pageSize=3;
    // 名字中包含 zhang
    String name = "zhang";
    // 正则表达式的匹配规则
    String regex = String.format("%s%s%s", "^.*", name, ".*$");
    Pattern pattern = Pattern.compile(regex,Pattern.CASE_INSENSITIVE);
    // 匹配正则
    Query query = new Query(Criteria.where("name").regex(pattern));
    // 分页查询
    List<User> users = mongoTemplate.find(query.skip((pageNum-1)*pageSize).limit(pageSize), User.class);
    System.out.println(users);
}
  • Upsert

    /**
     * 修改
     */
    @Test
    public void update(){
        //1. 首先查询
        User user = mongoTemplate.findById("625c1bbaa2f93370884eafeb", User.class);
        user.setName("lisi");
        // 改哪个?查出来!
        // 根据 id 查找要改的信息
        Query query = new Query(Criteria.where("id").is(user.getId()));
        // 怎么改?写出来!
        Update update = new Update();
        update.set("name",user.getName());
        UpdateResult upsert = mongoTemplate.upsert(query, update, User.class);
        long matchedCount = upsert.getMatchedCount();
        System.out.println("修改数量:"+matchedCount);
    }
    
  • Remove

    /**
     * 删除
     */
    @Test
    public void delete(){
        Query query = new Query(Criteria.where("id").is("625c1bbaa2f93370884eafeb"));
        mongoTemplate.remove(query,User.class);
    }