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版本,如果你需要最新版的可以去官方仓库下载
Redis可视化界面
Redis有两款可视化操作软件,根据自己的喜好来选择,到目前为止,RedisInsight-v2是Redis开源免费的版本,而RedisDesktopManager已经开始收费了,但下面这个版本还是免费版。
2. 配置Windows服务
配置Windows服务是为了之后使用Redis更加方便,不用每次需要用到Redis都要手动打开Redis服务,配置Windows之后,如果有服务需要用到Redis,Redis就会自行自动,方便本地开发。
打开Redis服务端的安装目录(如我安装redis在E:\studyAPP\Redis-3.2.100
),打开命令行,执行以下命令,表示将redis-server服务添加到Windows服务中,这样以后使用不需要再手动启动redis-server了。
1 redis-server --service-install redis.windows-service.conf --loglevel verbose
3. 测试连接
在命令行进入redis-cli客户端,看看能不能正常访问,如果出现拒绝访问,说明redis-server没有打开,我首次打开redis-cli就是访问失败,其实就是redis-server没有启动,需要去Windows服务手动打开一次,之后就不需要手动打开了。
此电脑-->管理-->服务和应用程序-->服务-->找到Redis-->右键属性-->启动类型改为自动-->自动服务
重新在命令行打开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数据库
密码如果在安装数据库没有设置的话,这里不需要填,如果有修改过密码则填入对应密码。
1 2 3 4 5 6 7 8 9 10 11 12 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中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import org.springframework.data.annotation.Id;import org.springframework.data.redis.core.RedisHash;import org.springframework.data.redis.core.index.Indexed;@RedisHash("persons") public class Person { @Id private String id; @Indexed private String firstname; @Indexed private String lastname; public Person () { } public Person (String firstname, String lastname) { this .firstname = firstname; this .lastname = lastname; } }
继承CrudRepository类
1 2 3 4 5 import com.itheima.domain.Person;import org.springframework.data.repository.CrudRepository;public interface PersonRepository extends CrudRepository <Person, String> {}
测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 ("小码" ,"同学" ); Person save = repository.save(person); System.out.println(save); } }
查看结果
RedisTemplate
RedisTemplate和JdbcTemplate差不多,都是Spring提供的框架,后面的学习中,你会发现,其他还有其他的类似XxxTemplate框架,这些都是有Spring提供的框架,SpringBoot将这些框架整合起来了,提高开发者效率,开发者需要那套功能就直接使用。
添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
反序列化操作需要jackson
依赖,而spring-boot-starter-web
启动依赖有jackson
依赖,如果你不需要进行反序列化操作可以不用spring-boot-starter-web
。
如果你设置了反序列化操作而不添加jackson
依赖,就会报如下错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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,则建议设置一下密码较好。
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: redis: port: 6379 host: localhost password: database: 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的反序列化怎么被实现呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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
类看看。
1 2 public class RedisTemplate <K, V> extends RedisAccessor implements RedisOperations <K, V>, BeanClassLoaderAware {}
可以看到,RedisTemplate继承了RedisAccessor,实现了RedisOperations和BeanClassLoaderAware接口,其中我们常用对Redis操作方法都在RedisOperations<K, V>
接口里面。
我们对上面这些常用的方法对一个简单的封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 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; public boolean hasKey (String key) { if (StringUtils.hasLength(key)){ return redisTemplate.hasKey(key); } return false ; } public boolean delKey (String key) { if (StringUtils.hasLength(key)){ return redisTemplate.delete(key); } return false ; } public boolean expTime (String key, long timeout, TimeUnit timeUnit) { if (StringUtils.hasLength(key)){ return redisTemplate.expire(key,timeout,timeUnit); } return false ; } public <T> boolean set (String key,T value) { if (StringUtils.hasLength(key)){ redisTemplate.opsForValue().set(key,value); return true ; } return false ; } public <T> T get (String key) { ValueOperations<String,T> valueOperations = redisTemplate.opsForValue(); return valueOperations.get(key); } public boolean redisCache (String key,Object value) { if (hasKey(key)){ return true ; }else { return set(key,value); } } }
添加实体类
用户后面对实体类测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import org.springframework.data.annotation.Id;import org.springframework.data.redis.core.RedisHash;import org.springframework.data.redis.core.index.Indexed;@RedisHash("persons") public class Person { @Id private String id; @Indexed 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 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; @Test public void addKey () { String key = "test" ; boolean b = redisService.set(key, "this is value" ); System.out.println(b); } @Test public void getKey () { String test = redisService.get("test" ); System.out.println(test); } @Test public void delKey () { String key = "test2" ; boolean b = redisService.delKey(key); System.out.println(b); } @Test public void setKeyAndTime () { String key = "test2" ; boolean test2 = redisService.set(key, "this is test2" ); boolean b = redisService.expTime(key, 1 , TimeUnit.MINUTES); System.out.println("set key:" +test2); System.out.println("set time:" +b); } @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); } @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 则把这些过程封装了起来,使用起来更加方便。
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 文件,输入内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 dbpath=E:\studyAPP\MongoDB\data\db logpath=E:\studyAPP\MongoDB\log\mongod.log logappend=true journal=true quiet=true port=27017
更多参数配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 systemLog: destination: file path: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/log/mongod.log" logAppend: true storage: journal: enabled: true dbPath: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/data" net: port: 27017 setParameter: enableLocalhostAuthBypass: false
安装mongo shell工具(可选)
mongo shell
工具只是为了方便我们调试使用,你如果不需要再命令行中对MongoDB操作,不安装也是可以的,但还是建议安装。
下载Shell工具,打开MongoDB官网:https://www.mongodb.com/try/download/shell
下载压缩包后,解压,将mongosh.exe
文件复制到刚刚安装MongoDB/bin
文件夹下,如下
运行MongoDB服务
1 mongod --dbpath E:\studyAPP\MongoDB\data\db
你也可以设置MongoDB的服务启动方式为延时启动,这样就不需要每次启动都要手动启动了,具体可以参考我的另一篇文章:https://blog.hikki.site/265cdd13.html
浏览器打开
http://localhost:27017/
Shell连接(可选)
使用Shell连接前提是安装了Mongo Shell才能使用该命令。
在命令提示符输入以下shell命令即可完成登陆
查看已经有的数据库
1 2 3 show databases show dbs
退出mongodb
更多参数可以通过帮助查看:
提示:MongoDB javascript shell是一个基于javascript的解释器,故是支持js程序的。
可视化操作
Studio 3T是一个MongoDB数据库管理工具,它提供了可视化操作界面、快速查询和聚合、自动完成和插件扩展等功能,使得开发人员和DBA能够更加高效地管理MongoDB数据库。
操作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用来封装子评论的内容,封装成三层结构。
封装后大概是这样的一个数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 { "_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
启动依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-mongodb</artifactId > </dependency >
配置MongoDB信息
创建application.yml
配置文件,创建工程默认配置文件是application.properties
,你可以直接将properties后缀改为yml。
1 2 3 4 5 6 spring: data: mongodb: port: 27017 host: localhost database: spring_db
创建实体类
下面我以创建一个博客评论的数据为例。
Result
我们使用Result
来封装每一篇文章下的评论,其中评论内容存储在data属性中,count则为data数组的长度,也就是该文章下有多少条评论数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; private List<Comment> data=null ; private String more=null ; private Integer count=0 ; }
在上面的类中,我们定义了一个data属性来储存评论内容,下面这个Comment就是评论的具体属性,评论中应该需要有用户名、头像、邮箱、点赞、子评论等待属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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; private String mailMd5; private String link; private String comment; private String os; private String browser; private String ipRegion; private boolean master; private Integer like=0 ; private List<SubComment> replies; private Integer top; private boolean isSpam=false ; private Date created=new Date (); }
在评论区中,每一条评论都可以被回复,被回复的内容就叫子评论,子评论我们使用SubComment来封装起来,子评论中不能存在子子评论,子评论之间互相回复使用rid和pid来判断识别回复用户。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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; private String mailMd5; private String link; private String comment; private String os; private String browser; private String ipRegion; private String master; private Integer like; private String rid; private String pid; private String ruser; private boolean isSpam=false ; private Date created=new Date (); }
接口实现
创建接口
创建一个评论管理的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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
模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 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; } @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; } @Override public Object updateComment (String rootId,String mailMd5, String newMailMd5) { Query query = new Query (Criteria.where("id" ).is(rootId)); query.addCriteria(Criteria.where("data" ).elemMatch(Criteria.where("mailMd5" ).is(mailMd5))); Update update = new Update ().set("data.$.mailMd5" , newMailMd5); UpdateResult result = mongoTemplate.updateMulti(query, update, Result.class); return result; } @Override public List<Result> findAllComment () { return mongoTemplate.findAll(Result.class); } @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(); } 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; } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 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; @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); } @Test void updateComment () { String rootId="646d69a1dc3b9f57424e311b" ; String mailMd5="646c7393588d857cce5479df" ; String newMailMd5="123456789" ; 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 () { Object commentById = commentDao.findCommentById("646d78c4cac1fa60cf0d9e42" ); System.out.println(commentById); } @Test void addSubComment () { SubComment comment = new SubComment (); 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.setLike(5 ); String rootId="646d69a1dc3b9f57424e311b" ; String commentId="646d78c4cac1fa60cf0d9e42" ; Object o = commentDao.addSubComment(rootId,commentId,comment); System.out.println(o); } }
总的来说,使用MongoDB来存储博客评论还是比较方便的,灵活的存储方式,如键值对、数组、嵌套对象,还支持全文搜索、聚合查询等高级功能。
主要也是MongoDB对于全文搜索的速度比较快,比MySQL快,如果在MySQL查询几十万或者几百万条数据中使用全文模糊查询,这速度就远不及MongoDB了。
Cache-SMS
前言
在实际应用中,我们有时候会用到邮箱验证,比如一个用户在使用邮箱注册时,我们需要使用一个邮箱给用户发送一个验证码邮件,并且这个验证码是有效期的,我们就可以使用缓存进行倒计时,下面我们使用Redis缓存技术进行一个简单的发送邮件验证码。
准备工作
添加依赖
我们需要用到Redis的依赖和Email依赖,打开pom.xml文件添加如下依赖并刷新一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-mail</artifactId > </dependency >
相关配置
我这里使用yml格式的配置方式,项目创建时默认是properties格式,你可以将后缀改为yml
1 2 3 4 5 6 7 8 9 spring: mail: host: smtp.163.com username: "aXXX56@163.com" password: "SEFVOSNLWDQALK" redis: password: host: 127.0 .0 .1 database: 2
创建Server服务
创建发送邮件服务类,记得给类上添加@Service
,同时,导入application.yml
的username值,作为发送邮件的发件人。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 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
你可以自己随机生成一个,至于是使用字母+数字
还是使用什么字符来发送,根据你的需求来做选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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(); } }
本文章来源于我的博客:https://blog.hikki.site