实战:五
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"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">template</span> <span class="hljs-attr">slot-scope</span>=<span class="hljs-string">"{ row }"</span>></span>
{{ row.dictCode }}
<span class="hljs-tag"></<span class="hljs-name">template</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-table-column</span>></span>
<span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">template</span> <span class="hljs-attr">slot-scope</span>=<span class="hljs-string">"scope"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">span</span>></span>{{ scope.row.value }}<span class="hljs-tag"></<span class="hljs-name">span</span>></span>
<span class="hljs-tag"></<span class="hljs-name">template</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-table-column</span>></span>
<span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">template</span> <span class="hljs-attr">slot-scope</span>=<span class="hljs-string">"scope"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">span</span>></span>{{ scope.row.createTime }}<span class="hljs-tag"></<span class="hljs-name">span</span>></span>
<span class="hljs-tag"></<span class="hljs-name">template</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-table-column</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-table</span>></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<Integer, CellData> 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"><<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>></span>
<span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">el-form-item</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"文件"</span>></span>
<span class="hljs-tag"><<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>
></span>
<span class="hljs-tag"><<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>></span>点击上传<span class="hljs-tag"></<span class="hljs-name">el-button</span>></span>
<span class="hljs-tag"><<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>></span>
只能上传exls文件,且不超过500kb
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-upload</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-form-item</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-form</span>></span>
<span class="hljs-tag"><<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>></span>
<span class="hljs-tag"><<span class="hljs-name">el-button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"dialogImportVisible = false"</span>></span> 取消 <span class="hljs-tag"></<span class="hljs-name">el-button</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">el-dialog</span>></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<Object, Object> <span class="hljs-title function_">redisTemplate</span><span class="hljs-params">(RedisConnectionFactory redisConnectionFactory)</span> { RedisTemplate<Object, Object> redisTemplate = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RedisTemplate</span><>(); 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<String> 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 对象,即一个文档认为就是一个对象。字段的数据类型是字符型,它的值除了使用基本的一些类型外,还可以包括其他文档、普通数组和文档数组。
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 位的字符串

-
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的精细查询的方法
*/
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);
}