Redis
Redis介绍
一个开源(BSD许可)的、内存中的数据结构存储系统,可用作数据库、缓存和消息中间件,并提供多种语言的API,是一个高性能的key-value数据库。
与其他key-value缓存产品相比有以下三个特点:
- 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
- 不仅仅支持简单的key-value类型的数据,同时还提供
list、set、zset、hash等数据结构的存储 - 支持数据的备份,即master-slave模式的数据备份
优点
-
存取速度快:Redis速度非常快,每秒可执行大约110000次的设值操作,或者执行81000次的读取操作
-
支持丰富的数据类型:Redis支持开发人员常用的大多数数据类型,例如列表、集合、排序集和散列等
-
操作具有原子性:所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新后的值
-
提供多种功能:Redis提供了多种功能特性,可用作非关系型数据库、缓存中间件、消息中间件等
Redis安装与配置
1. 安装软件
安装Redis服务端,这里蓝奏云的版本是5.0.10版本,如果你需要最新版的可以去官方仓库下载
{% note blue ‘fas fa-download’ flat %}
官方仓库:https://github.com/tporadowski/redis/releases
蓝奏云:https://rookie1679.lanzoum.com/ibpJM0wtyqvi
{% endnote %}
Redis可视化界面
Redis有两款可视化操作软件,根据自己的喜好来选择,到目前为止,RedisInsight-v2是Redis开源免费的版本,而RedisDesktopManager已经开始收费了,但下面这个版本还是免费版。
{% note blue ‘fas fa-download’ flat %}
RedisInsight-v2:https://rookie1679.lanzoum.com/iOeY90wtyhmf
RedisDesktopManager:https://rookie1679.lanzoum.com/iPU3s0wtypri
{% endnote %}
2. 配置Windows服务
配置Windows服务是为了之后使用Redis更加方便,不用每次需要用到Redis都要手动打开Redis服务,配置Windows之后,如果有服务需要用到Redis,Redis就会自行自动,方便本地开发。
打开Redis服务端的安装目录(如我安装redis在E:\studyAPP\Redis-3.2.100),打开命令行,执行以下命令,表示将redis-server服务添加到Windows服务中,这样以后使用不需要再手动启动redis-server了。
redis-server --service-install redis.windows-service.conf --loglevel verbose
3. 测试连接
在命令行进入redis-cli客户端,看看能不能正常访问,如果出现拒绝访问,说明redis-server没有打开,我首次打开redis-cli就是访问失败,其实就是redis-server没有启动,需要去Windows服务手动打开一次,之后就不需要手动打开了。
此电脑-->管理-->服务和应用程序-->服务-->找到Redis-->右键属性-->启动类型改为自动-->自动服务
20230519-453.png)
重新在命令行打开redis-cli

4. 客户端
RedisDesktopManager
redis默认是没有密码的,如果没有修改本机的配置,默认只允许本机访问,不需要其他机器远程访问,所以不需要密码是可以连接的。

连接到redis后,可以看到下面有16个数据库。

RedisInsight-v2
连接方式也差不多,点击添加数据库然后直接点连接就可以了。

Spring Data Redis
前言
Spring Data Redis
Spring Data Redis是Spring Framework的一个模块,它提供了对Redis数据库的整合和支持。Spring Data Redis基于Jedis和Lettuce客户端库,提供了Redis连接、数据操作和事务管理等功能,简化了Redis与Spring应用程序的集成。
RedisTemplate
RedisTemplate是Spring Data Redis中的核心组件之一,它是对Jedis和Lettuce客户端库的进一步封装,提供了对Redis操作的更高级别的抽象。RedisTemplate实现了Spring的高级数据访问接口(如RedisOperations、ListOperations、ValueOperations等),并提供了线程安全的Redis连接等功能。
Spring Data Redis是基于RedisTemplate的,可以通过RedisTemplate直接访问Redis数据库。除此之外,Spring Data Redis还提供了更多的特性和功能,如:
- 对Redis的数据结构(如Hashes、Sets、Zsets等)提供更加便捷的操作方法;
- 提供了对分布式锁和Lua脚本的支持;
- 支持Redis的发布订阅模式,可以方便地实现消息队列等功能;
- 支持Redis的集群模式,可以方便地横向扩展应用。
总的来说,Spring Data Redis提供了更加全面、高级的Redis集成功能和抽象,而RedisTemplate是Spring Data Redis的基础组件之一,提供了对Redis操作的高级别抽象和线程安全的连接管理。
配置Redis数据库
密码如果在安装数据库没有设置的话,这里不需要填,如果有修改过密码则填入对应密码。
spring:
redis:
port: 6379 # Redis服务器连接端口,默认是6379
host: localhost # Redis服务器地址
password: # Redis服务器连接密码(默认为空)
database: 0 # Redis数据库索引(默认为0)
lettuce:
pool:
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
编写实体类
编写一个实体类,用来将整个实体类的数据存储到Redis中。
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
@RedisHash("persons") // 指定操作实体类对象在Redis数据库中的存储空间
public class Person {
@Id // 标识实体类主键
private String id;
@Indexed // 标识对应属性在Redis数据库中生成二级索引
private String firstname;
@Indexed
private String lastname;
public Person() {
}
public Person(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
// 自行添加 set get toString方法
}
继承CrudRepository类
import com.itheima.domain.Person;
import org.springframework.data.repository.CrudRepository;
public interface PersonRepository extends CrudRepository<Person, String> {
}
测试方法
import site.hikki.domain.Person;
import site.hikki.repository.PersonRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class RedisTests {
@Autowired
private PersonRepository repository;
@Test
public void savePerson() {
Person person =new Person("小码","同学");
// 向Redis数据库添加数据
Person save = repository.save(person);
System.out.println(save);
}
}
查看结果

RedisTemplate
RedisTemplate和JdbcTemplate差不多,都是Spring提供的框架,后面的学习中,你会发现,其他还有其他的类似XxxTemplate框架,这些都是有Spring提供的框架,SpringBoot将这些框架整合起来了,提高开发者效率,开发者需要那套功能就直接使用。
添加依赖
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- web启动依赖,使用redis进行存储会用到jackson解析依赖,web启动依赖就有 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
反序列化操作需要jackson依赖,而spring-boot-starter-web启动依赖有jackson依赖,如果你不需要进行反序列化操作可以不用spring-boot-starter-web。
如果你设置了反序列化操作而不添加jackson依赖,就会报如下错误:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.core.RedisTemplate]: Factory method 'redisTemplate' threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/JsonSerializer
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
... 102 more
Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/JsonSerializer
at site.hikki.template.RedisConfig.redisTemplate(RedisConfig.java:24)
at site.hikki.template.RedisConfig$$EnhancerBySpringCGLIB$$82f29239.CGLIB$redisTemplate$0(<generated>)
at site.hikki.template.RedisConfig$$EnhancerBySpringCGLIB$$82f29239$$FastClassBySpringCGLIB$$1ab67068.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at site.hikki.template.RedisConfig$$EnhancerBySpringCGLIB$$82f29239.redisTemplate(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 103 more
Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.databind.JsonSerializer
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 114 more
配置Redis
在配置项application.yml配置文件中添加如下配置,redis刚安装时是默认没有密码的,值允许本机使用,不允许远程本地的redis,所以不设置密码也是安全的,如果你要远程redis,则建议设置一下密码较好。
spring:
redis:
port: 6379 # Redis服务器连接端口,默认是6379
host: localhost # Redis服务器地址
password: # Redis服务器连接密码(默认为空)
database: 0 # Redis数据库索引(默认为0)
lettuce: # 连接池
shutdown-timeout: 100ms
pool:
max-wait: PT10S # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-active: 100 # 连接池最大连接数(使用负值表示没有限制)
min-idle: 1 # 连接池中的最小空闲连接
max-idle: 30 # 连接池中的最大空闲连接
配置反序列化
我们正常在Java中对redis进行添加,都是被序列化过的,由ObjectOutputStream和ObjectInputStream类来实现,序列化的目的是在进行网络传输或者本地存储时保持数据完整性。
Spring Data Redis提供的RedisTemplate类来完成序列化和反序列化的操作,并且RedisTemplate封装了Redis的Java客户端,我们可以通过实现RedisSerializer接口并重写serialize和deserialize方法,然后在RedisTemplate中使用setKeySerializer和setValueSerializer方法设置自定义的序列化器。
创建一个RedisConfig类,将该类添加@Configuration注解,表示该类是一个配置类。
并且在实现RedisSerializer接口时,记得将该方法添加@Bean注解,表示将该方法收Ioc容器管理,不然Redis的反序列化怎么被实现呢?
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer=new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(genericJackson2JsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return template;
}
}
编写Redis方法
新建一个RedisService类,用于封装我们常用的Redis方法,后面使用直接调用即可。
新建RedisService类后,我们需要注入RedisTemplate实现类,我们打开RedisTemplate类看看。
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
}
可以看到,RedisTemplate继承了RedisAccessor,实现了RedisOperations和BeanClassLoaderAware接口,其中我们常用对Redis操作方法都在RedisOperations<K, V>接口里面。

我们对上面这些常用的方法对一个简单的封装。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
@Component
public class RedisService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 判断key是否存在
*/
public boolean hasKey(String key){
if (StringUtils.hasLength(key)){
return redisTemplate.hasKey(key);
}
return false;
}
/**
* 删除key
*/
public boolean delKey(String key){
if (StringUtils.hasLength(key)){
return redisTemplate.delete(key);
}
return false;
}
/**
* 设置key过期时间
* 形参timeUnit常见单位如下:
* TimeUnit.DAYS 天
* TimeUnit.HOURS 小时
* TimeUnit.MINUTES 分钟
* TimeUnit.SECONDS 秒
*/
public boolean expTime(String key, long timeout, TimeUnit timeUnit){
if (StringUtils.hasLength(key)){
return redisTemplate.expire(key,timeout,timeUnit);
}
return false;
}
/**
* 设置 -Set类型key
*/
public <T> boolean set(String key,T value){
if (StringUtils.hasLength(key)){
redisTemplate.opsForValue().set(key,value);
return true;
}
return false;
}
/**
* get -Set 类型key
*/
public <T> T get(String key){
ValueOperations<String,T> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
// -----------------------
/**
* 添加缓存
* @param key
* @param value
* @return
*/
public boolean redisCache(String key,Object value){
if (hasKey(key)){
return true;
}else {
return set(key,value);
}
}
}
添加实体类
用户后面对实体类测试
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
@RedisHash("persons") // 指定操作实体类对象在Redis数据库中的存储空间
public class Person {
@Id // 标识实体类主键
private String id;
@Indexed // 标识对应属性在Redis数据库中生成二级索引
private String firstname;
@Indexed
private String lastname;
public Person() {
}
public Person(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
@Override
public String toString() {
return "Person{" +
"id='" + id + '\'' +
", firstname='" + firstname + '\'' +
", lastname='" + lastname + '\'' +
'}';
}
}
测试Redis
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import site.hikki.pojo.Person;
import site.hikki.template.RedisService;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@SpringBootTest(classes = Application.class)
public class RedisTemplateTest {
@Resource
RedisService redisService;
/**
* 添加key
*/
@Test
public void addKey(){
String key ="test";
boolean b = redisService.set(key, "this is value");
System.out.println(b);
}
/**
* 获取key
*/
@Test
public void getKey(){
String test = redisService.get("test");
System.out.println(test);
}
/***
* 删除一个key
*/
@Test
public void delKey(){
String key = "test2";
boolean b = redisService.delKey(key);
System.out.println(b);
}
/**
* 设置Key并设置过期时间
*/
@Test
public void setKeyAndTime(){
String key = "test2";
boolean test2 = redisService.set(key, "this is test2");
// 给 test2的key设置过期时间为1分钟,一分钟后自动销毁test2
boolean b = redisService.expTime(key, 1, TimeUnit.MINUTES);
System.out.println("set key:"+test2);
System.out.println("set time:"+b);
}
/**
* 设置实体类类型的key
*/
@Test
public void setEntity(){
String key = "test3";
Person person = new Person();
person.setFirstname("小黑子");
person.setId("1");
person.setLastname("小白子");
boolean b = redisService.set(key, person);
System.out.println(b);
}
/**
* 获取实体类的key
*/
@Test
public void getEntity(){
String key = "test3";
Person person = redisService.get(key);
System.out.println(person.toString());
}
}
总的来说,RedisTemplates框架没也封装了非常多的方法,上面只是列举出常用的几个方法,你若还有其他的需求,你可以根据自己的需求来调用其他方法。
总结
RedisRepository API
它是在 Spring Data Redis 基础上封装了一层,提供了类似 JPA 的操作方式,可以通过继承 RedisRepository 接口快速实现基本的 CRUD 操作。同时,RedisRepository 还提供了诸如分页查询、按照属性名查找等常用功能。RedisRepository API 更适用于基础的 CRUD 操作和简单查询的场景。
RedisTemplate API
它是 Spring 对 Redis 的原生封装,提供了一系列操作 Redis 的方法,比如 PUT、GET、DEL、INCR 等。RedisTemplate API 相对来说操作更加灵活,支持 Redis 的所有数据结构和命令。RedisTemplate API 更适用于需要进行复杂操作的场景。
但需要注意的是,在使用 RedisTemplate API 时需要手动管理 Redis 的连接、事务,而 RedisRepository 则把这些过程封装了起来,使用起来更加方便。
{% note blue ‘fas fa-download’ flat %}
项目下载:https://rookie1679.lanzoum.com/i7xtp0wvloji
{% endnote %}
MongoDB
前言
MongoDB是一个开源、高性能、无模式的文档型数据库,它是NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库。
解决了什么
为什么出现MongoDB?
一个技术的出现,肯定是有原因的,这个技术是为了解决什么问题而出现,这才是我们关注的重点。
传统的关系型数据库在海量数据和高并发读写方面存在瓶颈问题,不能够很好的支持高性能查询。最常见的应用场景:
- 淘宝用户数据
- 存储位置:数据库
- 特征:永久性存储,修改频度极低
- 游戏装备数据、游戏道具数据
- 存储位置:数据库、Mongodb
- 特征:永久性存储与临时存储相结合、修改频度较高
- 直播数据、打赏数据、粉丝数据
- 存储位置:数据库、Mongodb
- 特征:永久性存储与临时存储相结合,修改频度极高
- 物联网数据
- 存储位置:Mongodb
- 特征:临时存储,修改频度飞速
这些场景都是存在海量的数据、结构复杂、并且对数据库频繁读写的要求,传统的关系型数据库有点吃力了,不能够很好的支持,MongoDB的出现,可以很好解决这些问题,读写速度快,支持高性能查询和聚合操作,可以总结为以下三点:
- 面向文档型数据模型 MongoDB采用面向文档的数据模型,可以将复杂的数据结构以嵌套文档和数组的形式保存在一个文档中,极大地简化了应用程序的数据处理过程,降低了对数据库操作的复杂度,提高了开发效率和灵活性。
- 分布式存储和高可用性 MongoDB支持自动分片和副本集等机制,可以轻松地实现跨多台服务器的分布式存储和高可用性,并且自带的故障转移功能和自动切换功能,保证了数据的安全性和可靠性。
- 高性能查询和聚合操作 MongoDB提供了强大而灵活的查询语言和聚合操作,可以快速地处理海量的数据,并支持基于索引的查询、数据聚合和MapReduce等高级功能,满足了对数据分析和数据挖掘方面的需求。
与Redis有什么不同
- 数据存储方式 Redis是一种基于内存的键值型数据库,数据全部存在内存中,并且可以将数据异步持久化到硬盘上。相比较而言,MongoDB是一种面向文档的数据库,数据以BSON(一种二进制的JSON格式)文档的形式存在,被组织在一个Collection中。
- 数据查询方式 Redis支持多种数据结构(如字符串、哈希、列表、集合、有序集合等),可以针对不同的数据结构提供不同的操作方法,如查找、排序、计数、分页、范围查询等。MongoDB支持复杂的文档型查询,可以使用内嵌文档、数组等方式来组织数据并进行查询,支持聚合操作和地理空间查询等高级功能。
- 数据一致性 Redis采用单线程模型,具有原子性和高并发性,但是在出现网络故障或者其他异常情况时,Redis会出现数据丢失或者数据不一致的情况。MongoDB采用多线程模型,在数据写入到主节点之后,会同步到所有的从节点上,保证数据的一致性和可靠性。
- 应用场景 由于Redis具有快速读写、高并发、低延迟的特点,因此适合于对数据查询频繁,但是写入操作较少的应用场景,如缓存、计数器、消息队列等。而MongoDB适用于需要海量存储、数据结构复杂、查询灵活的应用场景,如数据分析、日志存储、内容管理、社交网络等。
安装MongoDB
下载安装包
MongoDB 提供了可用于 32 位和 64 位系统的预编译二进制包,你可以从MongoDB官网下载安装,MongoDB 安装包。
下载地址:https://www.mongodb.com/try/download/community

根据上图所示下载zip包。
提示:版本的选择:
MongoDB的版本命名规范如:x.y.z;
y为奇数时表示当前版本为开发版,如:1.5.2、4.1.13;
y为偶数时表示当前版本为稳定版,如:1.6.3、4.0.10;
z是修正版本号,数字越大越好。
详情:http://docs.mongodb.org/manual/release-notes/#release-version-numbers
安装选择Custom

在安装过程中,在上面这步需要选择,其他都是默认下一步即可。
新建文件和文件夹
在/data文件夹里面新建一个db文件夹,用于存储数据文件

新建一个mongo.config文件,输入内容如下:
dbpath=E:\studyAPP\MongoDB\data\db #数据库路径
# 这里是你的安装路径!!!!!
logpath=E:\studyAPP\MongoDB\log\mongod.log # 日志输出文件路径
# 这里是你的安装路径!!!!!
logappend=true #错误日志采用追加模式
journal=true #启用日志文件,默认启用
quiet=true #过滤掉无用的日志信息,若需要调试使用请设置为false
port=27017 #端口号 默认为27017
更多参数配置:
systemLog:
destination: file
#The path of the log file to which mongod or mongos should send all diagnostic logging information
path: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/log/mongod.log"
logAppend: true
storage:
journal:
enabled: true
#The directory where the mongod instance stores its data.Default Value is "/data/db".
dbPath: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/data"
net:
#bindIp: 127.0.0.
port: 27017
setParameter:
enableLocalhostAuthBypass: false
安装mongo shell工具(可选)
mongo shell工具只是为了方便我们调试使用,你如果不需要再命令行中对MongoDB操作,不安装也是可以的,但还是建议安装。
下载Shell工具,打开MongoDB官网:https://www.mongodb.com/try/download/shell

下载压缩包后,解压,将mongosh.exe文件复制到刚刚安装MongoDB/bin文件夹下,如下

运行MongoDB服务
mongod --dbpath E:\studyAPP\MongoDB\data\db

你也可以设置MongoDB的服务启动方式为延时启动,这样就不需要每次启动都要手动启动了,具体可以参考我的另一篇文章:https://blog.hikki.site/265cdd13.html
浏览器打开

Shell连接(可选)
使用Shell连接前提是安装了Mongo Shell才能使用该命令。
在命令提示符输入以下shell命令即可完成登陆
mongosh

查看已经有的数据库
show databases
# 或者
show dbs
退出mongodb
exit
更多参数可以通过帮助查看:
mongo --help
提示:MongoDB javascript shell是一个基于javascript的解释器,故是支持js程序的。
可视化操作
Studio 3T是一个MongoDB数据库管理工具,它提供了可视化操作界面、快速查询和聚合、自动完成和插件扩展等功能,使得开发人员和DBA能够更加高效地管理MongoDB数据库。
{% note blue ‘fas fa-download’ flat %}
Studio 3T下载:https://studio3t.com/download/
{% endnote %}
操作MongoDB数据库,使用的不是SQL语句查询,而是使用类似json获取字段这种类型来或者MongoDB数据,对json不是很熟悉的你可能有点陌生,Studio 3T就类似Navicat数据库客户端软件,可以很方便的对数据库进行操作
简单使用

整合MongoDB
此次试验我们使用Spring Data MongoDB数据库框架,Spring Data MongoDB是Spring Data家族的一员,提供了一套用于与MongoDB交互的API,可以轻松地实现MongoDB的CRUD操作和高级查询等功能。Spring Data MongoDB还支持异步操作、分页查询、文本搜索等高级功能,非常适合在各种规模的企业项目中使用。
除了Spring Data MongoDB,也有很多其他的MongoDB框架可供选择,如Morphia、MongoJack、ReactiveMongo等,在不同的场景下可选用不同的框架以满足项目需求。
MongoDB实现方式
Spring Data MongoDB 中默认提供了两种 API 实现方式:MongoTemplate 和 MongoRepository。
MongoTemplate API
它是 Spring 对 MongoDB 的原生封装,提供了与 MongoDB 基本操作的一一对应方法,比如插入、查找、修改、删除等。MongoTemplate API 提供了比较灵活的操作方式,适用于需要进行复杂操作的场景。
MongoRepository API
它是在 MongoTemplate API 基础上封装了一层,提供了更简洁易用的接口,支持类似 JPA 的操作方式。通过继承 MongoRepository 接口可以快速地实现基础的 CRUD 操作,同时还提供了根据属性名自动生成查询语句等功能。MongoRepository API 更适用于基础的 CRUD 操作和简单查询的场景。
实现思路
在Java中整合MongoDB,跟上面学习Redis的RedisTemplate开发是一样的步骤,只不过是换了一个数据库而已,首先还是导入依赖-->配置MongoDB数据库信息-->编写MongoDB实现类-->测试结果,步骤是差不多的,下面的例子,我会演示一个简单的评论系统,将博客评论的数据存储到MongoDB中。
评论存在用户名、邮箱、评论之间相互回复、评论者用户组等等相关信息,并且数据在前后端进行传输,肯定也是需要封装起来的。
基于上面的这个情况,我们创建几个类来封装一下信息。
- Result实体类用来封装数据传输的基本框架
- Result下有个data属性,该属性用来存储所有评论内容,包括子评论
- 创建一个Comment实体类用来封装评论信息,Comment类下有个replies属性,该属性用来存储子评论的内容
- 子评论内容使用一个SubComment实体类来封装内容
总的来说,需要三个实体类来封装内容,Result用来封装整体内容传输基本框架,Comment用来封装每一个博客评论内容,SubComment用来封装子评论的内容,封装成三层结构。
封装后大概是这样的一个数据结构:
{
"_id" : ObjectId("646d69a1dc3b9f57424e311b"),
"data" : [
{
"_id" : ObjectId("646d78c4ac1fa60c0d9e42"),
"nick" : "小码同学",
"avatar" : "https://blog.hikki.site/avatar.png",
"mailMd5" : "646c79358cce5479df",
"link" : "https://blog.hikki.site",
"comment" : "这博客好棒啊",
"os" : "Android",
"browser" : "Firefox/113.0",
"ipRegion" : "192.168.2.8",
"master" : true,
"like" : 5,
"replies" : [
{
"_id" : ObjectId("646d81a4f2e6764ad1e7fb50"),
"nick" : "小码同学",
"avatar" : "https://blog.hikki.site/avatar.png",
"mailMd5" : "646c8d857cce479df",
"link" : "https://blog.hikki.site",
"comment" : "这博客好棒啊",
"os" : "Android",
"browser" : "Firefox/113.0",
"ipRegion" : "192.168.2.8",
"like" : 5,
"isSpam" : false,
"created" : ISODate("2023-05-24T03:16:52.975+0000")
}
],
"more" : "",
"count" : 0,
"_class" : "site.hikki.pojo.cm.Result",
"mailMd5" : "69a1dc3b9f574"
}
配置MongoDB
导入Maven依赖
打开pom.xml配置文件导入spring-boot-starter-data-mongodb启动依赖
<!-- MongoDB-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
配置MongoDB信息
创建application.yml配置文件,创建工程默认配置文件是application.properties,你可以直接将properties后缀改为yml。
spring:
data:
mongodb:
port: 27017
host: localhost
database: spring_db
创建实体类
下面我以创建一个博客评论的数据为例。
Result
我们使用Result来封装每一篇文章下的评论,其中评论内容存储在data属性中,count则为data数组的长度,也就是该文章下有多少条评论数据。
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Data
@Document()
public class Result {
@Id
private String id; // 主键ID存储到数据库默认是ObjectId()
private List<Comment> data=null; //评论内容
private String more=null;
private Integer count=0; //data数组下条数,也就是该文章评论数量
}
Comment
在上面的类中,我们定义了一个data属性来储存评论内容,下面这个Comment就是评论的具体属性,评论中应该需要有用户名、头像、邮箱、点赞、子评论等待属性。
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.util.Date;
import java.util.List;
/**
* 父评论 也叫根评论
*/
@Data
public class Comment {
@Id
private String id; //主键
private String nick; // 用户别名
private String avatar; // 头像URL
private String mailMd5; // 邮箱Hash
private String link; // 用户网站地址
private String comment; // 评论内容
private String os; // 评论所用系统
private String browser; // 浏览器版本
private String ipRegion; // 发布者IP地址
private boolean master;// 是否是博客这座
private Integer like=0; // 点赞数量
private List<SubComment> replies; // 子评论
private Integer top; // 置顶数量,数值越小,置顶优先权越高
private boolean isSpam=false; // 是否是垃圾邮件
private Date created=new Date(); // 创建时间
}
SubComment
在评论区中,每一条评论都可以被回复,被回复的内容就叫子评论,子评论我们使用SubComment来封装起来,子评论中不能存在子子评论,子评论之间互相回复使用rid和pid来判断识别回复用户。
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.util.Date;
@Data
public class SubComment {
@Id
private String id; //主键
private String nick; // 用户别名
private String avatar; // 头像URL
private String mailMd5; // 邮箱Hash
private String link; // 用户网站地址
private String comment; // 评论内容
private String os; // 评论所用系统
private String browser; // 浏览器版本
private String ipRegion; // IP 地址
private String master;// 是否是博客这座
private Integer like; // 点赞数量
private String rid; //该评论的根父评论
private String pid; //要回复的作者ID
private String ruser; //该评论回复的是谁
private boolean isSpam=false; //该邮箱是否是垃圾邮箱
private Date created=new Date(); // 创建时间
}
接口实现
创建接口
创建一个评论管理的接口。
import site.hikki.pojo.cm.Comment;
import site.hikki.pojo.cm.Result;
import site.hikki.pojo.cm.SubComment;
import java.util.List;
public interface CommentDao {
Result initResult();
Object addComment(String rootId,Comment comment);
Object delComment(String rootId,String commentId);
Object updateComment(String rootId,String mailMd5, String newMailMd5);
List<Result> findAllComment();
List<Comment> findCommentById(String id);
Object addSubComment(String rootId,String commentId,SubComment subComment);
}
实现接口
显示刚刚创建的接口,使用MongoTemplate模板
import com.mongodb.client.result.UpdateResult;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.*;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.*;
import org.springframework.stereotype.Repository;
import site.hikki.dao.CommentDao;
import site.hikki.pojo.cm.*;
import java.util.ArrayList;
import java.util.List;
@Repository
public class CommentDaoImpl implements CommentDao {
@Autowired
MongoTemplate mongoTemplate;
/**
* 初始化每一条博客文章
*/
@Override
public Result initResult() {
Result result = new Result();
result.setCount(0);
result.setMore("");
result.setData(new ArrayList<>());
return mongoTemplate.insert(result);
}
/**
* 添加父评论
*/
@Override
public Object addComment(String rootId,Comment comment) {
// 此处的
Query query = new Query(Criteria.where("id").is(rootId));
Update update = new Update().push("data",comment);
UpdateResult result = mongoTemplate.updateFirst(query, update, Result.class);
return result;
}
/**
*
* 删除时,如果是自带生成的_id,不需要添加 ObjectId()
* 直接使用 ObjectId 的值即可
* @param rootId 每条评论对应的文档ID
* @param commentId 父评论
* @return
*/
@Override
public Object delComment(String rootId,String commentId) {
Query query = new Query(Criteria.where("id").is(rootId));
Update update = new Update().pull("data", Query.query(Criteria.where("_id").is(commentId)));
UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Result.class);
return updateResult;
}
/**
* 根据父评论Id 修改父评论邮件 Hash
* @param rootId Result根ID
* @param mailMd5 邮件原Hash值
* @param newMailMd5 需要更换后的邮件Hash值
*/
@Override
public Object updateComment(String rootId,String mailMd5, String newMailMd5) {
Query query = new Query(Criteria.where("id").is(rootId));
// 使用elemMatch操作符选择匹配的数组元素
query.addCriteria(Criteria.where("data").elemMatch(Criteria.where("mailMd5").is(mailMd5)));
// 使用set运算符更新所选数组元素的“mailMd5”字段
Update update = new Update().set("data.$.mailMd5", newMailMd5);
UpdateResult result = mongoTemplate.updateMulti(query, update, Result.class);
return result;
}
/**
* 查询整个Comment文档数据
*/
@Override
public List<Result> findAllComment() {
return mongoTemplate.findAll(Result.class);
}
/**
* 根据评论ID查询
* @param commentId 父评论ID
*/
@Override
public List<Comment> findCommentById(String commentId) {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.unwind("data"),
Aggregation.match(Criteria.where("data._id").is(new ObjectId(commentId))),
Aggregation.project()
.and("data.nick").as("nick")
.and("data.avatar").as("avatar")
.and("data.mailMd5").as("mailMd5")
.and("data.link").as("link")
.and("data.comment").as("comment")
.and("data.os").as("os")
.and("data.browser").as("browser")
.and("data.ipRegion").as("ipRegion")
.and("data.master").as("master")
.and("data.like").as("like")
.and("data.replies").as("replies")
.and("data.top").as("top")
.and("data.isSpam").as("isSpam")
.and("data.created").as("created")
);
AggregationResults<Comment> result = mongoTemplate.aggregate(aggregation, "result", Comment.class);
return result.getMappedResults();
}
/**
* 添加子评论
* 子评论存储在replies下
* @param rootId Result文档根Id
* @param commentId 父评论ID
* @param subComment 子评论内容
* @return UpdateResult
*/
public Object addSubComment(String rootId,String commentId,SubComment subComment){
Query query = Query.query(Criteria.where("_id").is(rootId));
query.addCriteria(Criteria.where("data._id").is(commentId));
Update update = new Update().push("data.$.replies", subComment);
UpdateResult result = mongoTemplate.updateFirst(query, update, Result.class);
return result;
}
}
测试
import org.bson.types.ObjectId;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import site.hikki.Application;
import site.hikki.pojo.cm.*;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest(classes = Application.class)
class CommentDaoTest {
@Autowired
CommentDao commentDao;
//TODO: 该测试必须先运行 initResult 方法,自动创建 Comment 文档
@Test
public void initResult(){
Result result = commentDao.initResult();
System.out.println(result);
}
@Test
void addComment() {
Comment comment = new Comment();
comment.setId(new ObjectId().toString());
comment.setNick("小码同学");
comment.setAvatar("https://blog.hikki.site/avatar.png");
comment.setMailMd5("646c7393588d857cce5479df");
comment.setLink("https://blog.hikki.site");
comment.setComment("这博客好棒啊");
comment.setOs("Android");
comment.setBrowser("Firefox/113.0");
comment.setIpRegion("192.168.2.8");
comment.setMaster(true);
comment.setReplies(new ArrayList<>());
comment.setLike(5);
comment.setTop(1);
ArrayList<Comment> comments = new ArrayList<>();
comments.add(comment);
String rootId = "646d69a1dc3b9f57424e311b";
commentDao.addComment(rootId,comment);
}
/**
* 删除父评论
*
*/
@Test
void delComment() {
String rootId="646d69a1dc3b9f57424e311b";
String commentId="646d69ef9c64d41e198f7e6b";
Object o = commentDao.delComment(rootId, commentId);
System.out.println(o);
}
/**
* 修改data下的父评论的邮箱HAsh
*/
@Test
void updateComment() {
String rootId="646d69a1dc3b9f57424e311b"; // Result根ID
String mailMd5="646c7393588d857cce5479df"; // 邮件原Hash值
String newMailMd5="123456789"; // 需要更换后的邮件Hash值
Object o = commentDao.updateComment(rootId,mailMd5,newMailMd5);
System.out.println(o);
}
@Test
void findAllComment() {
List<Result> allComment = commentDao.findAllComment();
for (Result c : allComment) {
System.out.println(c);
}
}
@Test
void findCommentById() {
// 根据父评论ID查询
Object commentById = commentDao.findCommentById("646d78c4cac1fa60cf0d9e42");
System.out.println(commentById);
}
@Test
void addSubComment(){
SubComment comment = new SubComment(); // 子评论内容
comment.setId(new ObjectId().toString()); // 子评论自动生成ID
comment.setNick("小码同学");
comment.setAvatar("https://blog.hikki.site/avatar.png");
comment.setMailMd5("646c7393588d857cce5479df");
comment.setLink("https://blog.hikki.site");
comment.setComment("这博客好棒啊");
comment.setOs("Android");
comment.setBrowser("Firefox/113.0");
comment.setIpRegion("192.168.2.8");
comment.setLike(5);
String rootId="646d69a1dc3b9f57424e311b"; //Result文档根Id
String commentId="646d78c4cac1fa60cf0d9e42"; //父评论ID
Object o = commentDao.addSubComment(rootId,commentId,comment);
System.out.println(o);
}
}
总的来说,使用MongoDB来存储博客评论还是比较方便的,灵活的存储方式,如键值对、数组、嵌套对象,还支持全文搜索、聚合查询等高级功能。
主要也是MongoDB对于全文搜索的速度比较快,比MySQL快,如果在MySQL查询几十万或者几百万条数据中使用全文模糊查询,这速度就远不及MongoDB了。
{% note blue ‘fas fa-download’ flat %}
项目下载:https://rookie1679.lanzoum.com/iBzHF0x4562b
{% endnote %}
Cache-SMS
前言
在实际应用中,我们有时候会用到邮箱验证,比如一个用户在使用邮箱注册时,我们需要使用一个邮箱给用户发送一个验证码邮件,并且这个验证码是有效期的,我们就可以使用缓存进行倒计时,下面我们使用Redis缓存技术进行一个简单的发送邮件验证码。
准备工作
添加依赖
我们需要用到Redis的依赖和Email依赖,打开pom.xml文件添加如下依赖并刷新一下。
<!-- Spring web项目-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- email邮箱-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
相关配置
我这里使用yml格式的配置方式,项目创建时默认是properties格式,你可以将后缀改为yml
spring:
mail:
host: smtp.163.com #
username: "aXXX56@163.com" #发件人
password: "SEFVOSNLWDQALK" #发件人密码(最好是使用授权码)
redis:
password:
host: 127.0.0.1
database: 2 # 指定Redis数据库
创建Server服务
创建发送邮件服务类,记得给类上添加@Service,同时,导入application.yml的username值,作为发送邮件的发件人。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.mail.internet.MimeMessage;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Service
public class MailService {
@Autowired
private JavaMailSender javaMailSender;
@Autowired
private RedisTemplate redisTemplate;
// 发件人(系统自带发件)
@Value("${spring.mail.username}")
private String fromUser;
//标题
private String subject = "小码博客身份验证码";
//正文
String nowTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
/**
*
* @param emailCode 验证码
* @param toUser 收件人(游客)
*/
public void sendMail(String emailCode, String toUser) {
// 缓存验证码code
redisTemplate.opsForValue().set("code",emailCode);
// 设置验证码有效5分钟
redisTemplate.expire("code",5, TimeUnit.MINUTES);
System.out.println(fromUser);
System.out.println(toUser);
String context ="<div style='margin:10px auto 20px;border-radius:5px;background:#fff; box-shadow:0 0 10px #b1b1b1; text-align:left;'><div style='margin:0 40px; color:#999; border-bottom:1px dotted #DDD; padding:40px 0 30px; font-size:13px; text-align:center;'><a href='https://blog.hikki.site' style='text-decoration:none;display:inline-flex;' on_e-link-mark='yes'><img src='http://localhost:8080/BlogDemo_war_exploded/component/img/emailavatar.png' alt='小码博客' style='width:64px;height:64px;'><span style='line-height:24px; margin-left:15px; height:64px; overflow:hidden; text-align:left; max-width: 360px;'><span style='color:#555; font-size:20px; margin-top:12px; display:inline-block;'>小码博客</span><br><span style='color:#999'>——用户注册</span></span></a></div><div style='padding:30px 40px 40px; min-height:280px;'><p style='margin:0px; margin-bottom:20px;'><a data-auto-link='1' href='mailto:"+toUser+"'>"+toUser+"</a>,您好,</p><p style='margin:0px;'>您正在进行邮箱验证,本次请求的验证码如下,为了保障您帐号的安全性,请及时完成验证。</p><p style='margin:0px; text-align:center; margin-top:40px;'><span style='padding:8px 16px;border-radius:3px;text-align:center;text-decoration:none;background-color:#ecf4fb;color:#1890ff;font-size:18px; font-weight:700; letter-spacing:2px; margin:0;white-space:nowrap;display:inline-block;'>"+emailCode+"</span></p></div><div style='padding:20px 40px 40px;'><p>小码博客</p><p>"+nowTime+"</p></div></div>";
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message,true);
helper.setFrom(fromUser+"(邮箱验证)"); // 邮箱主题名
helper.setTo(toUser);
helper.setSubject(subject);
helper.setText(context,true);
//添加发送附件
// File f1 = new File("D:\\workspace\\springboot\\springboot_23_mail\\target\\springboot_23_mail-0.0.1-SNAPSHOT.jar");
// File f2 = new File("D:\\workspace\\springboot\\springboot_23_mail\\src\\main\\resources\\logo.png");
//
// helper.addAttachment(f1.getName(),f1);
// helper.addAttachment("这是图片.png",f2);
javaMailSender.send(message);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getCode() {
return (String) redisTemplate.opsForValue().get("code");
}
public static void main(String[] args) {
}
}
创建控制器
使用一个控制来发送验证码,此处的code你可以自己随机生成一个,至于是使用字母+数字还是使用什么字符来发送,根据你的需求来做选择。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import site.hikki.service.MailService;
@RestController
@RequestMapping
public class SendEmail {
@Autowired
private MailService send;
@GetMapping("/send")
public String send(){
String code ="46511";
send.sendMail(code,"1341151056@qq.com");
return "hello 小码同学!!!";
}
@GetMapping("/get")
public String getCOde(){
System.out.println(send.getCode());
return send.getCode();
}
}
{% note blue ‘fas fa-download’ flat %}
项目下载:https://rookie1679.lanzoum.com/ircNb0y8bvoj
{% endnote %}
本文章来源于我的博客:https://blog.hikki.site

