学习来源:B站黑马程序员免费视频
Redis的一些Java客户端
客户端 | 特点 |
Jedis | 以Redis命令作为方法名称,学习成本低,简单实用。但是Jedis实例是线程不安全的,多线程环境下需要基于连搜池来使用 |
Lettuce | Lettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。 |
Redisson | Redisson是一个基于Redis实现的分布式、可伸缩的Java数据结构集合,包含了诺如Map,0ueue、LockSemaphore、AtomicLong等强大功能 |
Spring Data Redis | 兼容Jedis和Lettuce |
Jedis的Github网址
Jedis的官网地址:https://github.com/redis/jedis。
Jedis学习-配置并测试能否运行Jedis简单代码
1.先创建一个maven项目,在其pom文件引入如下依赖:
<dependency>
<groupId>redis.clients </groupId>
<artifactId>jedis </artifactId>
<version>5.2.0 </version>
</dependency>
<!––单元测试––>
<dependency>
<groupId>org.junit.jupiter </groupId>
<artifactId>junit-jupiter </artifactId>
<version>5.7.0 </version>
<scope>test </scope>
</dependency>
2.创造一个Test_Jedis测试类来测试。
public class Test_Jedis { private Jedis jedis; @BeforeEach void setUp() { //1.建立连接 jedis = new Jedis("localhost", 6379); //2.设置密码 jedis.auth("123456"); //3.选择库 jedis.select(0); } @Test void teststring_set_get(){ //存储数据 String result = jedis.set("name","hello"); System.out.println("result = " + result); //获取数据 String name = jedis.get("name"); System.out.println("name = " + name); } @AfterEach void tearDown(){ if(jedis != null){ jedis.close(); } } }
3.测试输出:
result = OK name = hello
测试成功!!!
由此可知,Jedis的使用步骤为:
- 引入依赖
- 创建Jedis对象,建立连接
- 使用Jedis,方法名与Redis命令一致
- 释放资源
Jedis学习-Jedis连接池
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此推荐使用Jedis连接池代替Jedis的直连方式。
创建一个连接池类的简单代码:
public class JedisConnectionFactory { private static final JedisPool jedisPool; static { //配置连接池 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(8);//最大连接数 config.setMaxIdle(8);//最大空闲连接数 config.setMinIdle(0);//最小空闲连接数 config.setMaxWait(Duration.ofSeconds(1));// 最大等待时间 //创建连接池对象 jedisPool = new JedisPool(config, "127.0.0.1", 6379, 1000, "123456"); } public static Jedis getJedis() { return jedisPool.getResource(); } }
可以看出创建Jedis大致步骤为:
- 创建一个自定义Jedis连接池类JedisConnectionFactory
- 在类中通过静态代码块创建Jedis连接池配置对象
- 为Jedis配置对象设置相应配置属性
- 用Jedis配置对象和其他参数(如:Redis的IP地址和端口、连接超时时长、密码)创建Jedis连接池对象
- 在类中创建一个静态方法返回Jedis连接池资源
接下来我们来测试一下,我们的JedisConnectionFactory类是否能正常工作。
直接在刚刚的Test_Jedis测试类中,将new 一个Jedis对象变成通过JedisConnectionFactory类返回连接池资源即可,具体代码如下。
//1.建立连接jedis = new Jedis("localhost", 6379);jedis = JedisConnectionFactory.getJedis();
测试输出:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
result = OK
name = hello
可以看到输出结果是正确的,不过却出现了一个问题——有几段红字出现,询问Chatpgt得知原因如下:
这个错误信息来自 SLF4J(Simple Logging Facade for Java),它提示在使用 SLF4J 时未找到 org.slf4j.impl.StaticLoggerBinder
类,因此无法初始化实际的日志实现。这通常表示没有引入具体的日志实现库,因此 SLF4J 默认使用空操作(NOP)记录器,导致日志输出不起作用。
看起来是我们缺少依赖导致的,解决起来也很简单,在pom文件添加一下相关依赖即可,我的代码如下:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.9</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.12</version> </dependency>
接下来测试一下输出:
result = OK name = hello
可见红字没有了,问题解决!!!
一点小提醒
Jedis对象的close方法的内部代码如下:
public void close() { if (this.dataSource != null) { Poolpool = this.dataSource; this.dataSource = null; if (this.isBroken()) { pool.returnBrokenResource(this); } else { pool.returnResource(this); } } else { this.connection.close(); } }
可以看到当我们使用Jedis连接池时,close方法内部是归还连接池资源而不是关闭连接。
SpringDataRedis学习-了解SpringDataRedis
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis
特点:
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK.JSON.字符串.Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
API | 返回值类型 | 说明 |
redisTemplate.opsForValue() | ValueOperations | 操作string类型数据 |
redisTemplate.opsForHash() | HashOperations | 操伦Hash类型数据 |
redisTemplate.opsForList() | ListOperations | 操作List类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作Set类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作Sortedset类型数据 |
redisTemplate | 通用的命令 |
SpringDataRedis学习-配置和简单使用
SpringDataRedis的简单使用步骤:
1.引入spring-boot-starter-data-redis和commons-pool2依赖。
<!--spring-boot-starter-data-redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--common-pool--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
2.在application.properties文件配置Redis信息。
#主机 spring.data.redis.host=127.0.0.1 #端口 spring.data.redis.port=6379 #密码 spring.data.redis.password=123456 #选择数据库0 spring.data.redis.database=0 #启用lettuce连接池 spring.data.redis.lettuce.pool.enabled=true #连接池最大连接数量 spring.data.redis.lettuce.pool.max-active=8 #连接池最大空闲连接数量 spring.data.redis.lettuce.pool.max-idle=8 #连接池最小空闲连接数量 spring.data.redis.lettuce.pool.min-idle=0 #连接池连接等待时间 spring.data.redis.lettuce.pool.max-wait=100ms
3.注入RedisTemplate并使用,这里只是测试一下。
@SpringBootTest class SpringDataRedisApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void testString() { //写入一条string数据 redisTemplate.opsForValue().set("name", "虎哥"); //获取string数据 Object name = redisTemplate.opsForValue().get("name"); System.out.println("name = " + name); } }
输出结果:
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap
classpath has been appended
name = 虎哥
测试成功!但是这里又有红色信息出现,翻译过来大概是Java HotSpot(TM)64位服务器虚拟机警告:共享仅支持引导加载程序类,因为已附加引导程序类路径。稍微查了一下,具体解决方案可以参考如下网址。
https://www.didispace.com/article/richang/20231014-VM_warning_Sharing.html
实测里面第一个方法有效。
SpringDataRedis学习-SpringDataRedis的序列化方式
RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化。我们可以在Redis数据库里查到我们的插入的(键, 值)不是(name, 虎哥),而是(\xac\xed\x00\x05t\x00\x04name, \xAC\xED\x00\x05t\x00\x06\xE8\x99\x8E\xE5\x93\xA5)。
虽然,这对于我们通过SpringDataRedis来查询和修改这对键值对没有影响,但是有两个缺点:
- 可读性差
- 内存占用大
为了解决这样的问题,我们就需要自定义RedisTemplate的序列化方式,具体代码如下。
@Configuration public class RedisConfig { @Bean public RedisTemplateredisTemplate(RedisConnectionFactory connectionFactory) { //创建RedisTemplate对象 RedisTemplate template = new RedisTemplate<>(); //设置连接工厂 template.setConnectionFactory(connectionFactory); //创建JSON序列化工具 GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); //设置Key的序列化 template.setKeySerializer(RedisSerializer.string()); template.setHashKeySerializer(RedisSerializer.string()); //设置Value的序列化 template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); //返回 return template; } }
这段代码的目的是为了通过@Bean 注解将我们自定义了序列化方式的RedisTemplate对象注册到 Spring 容器中,方便在应用的各个部分使用该对象。
如何使用呢?很简单,只需要使用@Autowired注解即可。以刚刚展示的SpringDataRedis的简单使用步骤中第3步的代码为例,将@Autowired注解下面的RedisTemplate后面加个<String, Object>,这里的redisTemplate就会变成我们自定义序列化方式了的对象。
@Autowiredprivate RedisTemplate redisTemplate;@Autowired private RedisTemplate<String, Object> redisTemplate;
输出结果:
name = 虎哥
成功!!!
小提一下,因为我们自定义的RedisTemplate对象支持<String, Object>类型的键值对,所以Value可以是一个类对象。
运行时可能出现的两种报错及解决方法:
- 如果错误提示找不到我们注册的Bean,大概率是因为我们定义的RedisConfig类,不在主函数类的同目录或者子目录下。
- 如果错误是com.fasterxml.jackson.core.JsonProcessingException,那大概率是因为没有在pom文件引入Jackson的相关依赖。
引入Jackson依赖代码如下:
<!––Jackson––> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson–databind</artifactId> </dependency>
不过虽然我们使用Json序列化器能支持Object类型的Value,但它为了能实现自动反序列化,当我们传一个类对象为Value的话,它会私自给这个Value加了一条类名数据。
例如传的是一个User类的Value,User类定义如下:
@Data @NoArgsConstructor @AllArgsConstructor public class User { private String name; private Integer age; }
但是实际存储在Redis中的是Value值如下:
{ "@class": "com.liu.redis.springdataredis.pojo.User", "name": "虎哥", "age": 18 }
可见多了一条数据:”@class”: “com.liu.redis.springdataredis.pojo.User”。
这样也会浪费内存空间,所以为了节省内存空间。我们一般并不会使用JSON序列化器来处理Value,而是统一使用String序列化器,要求只能存储String类型的Key和Value,当需要存储Java对象时,手动完成对象的序列化和反序列化。
具体可以按照如下测试代码:
@SpringBootTest class SpringDataRedisApplicationTests { @Autowired private StringRedisTemplate stringRedisTemplate; private static final ObjectMapper objectMapper = new ObjectMapper(); @Test void testString() throws JsonProcessingException { //创建对象 User user = new User("虎哥", 18); //手动序列化 String json = objectMapper.writeValueAsString(user); //写入数据 stringRedisTemplate.opsForValue().set("user:100", json); //获取数据 json = stringRedisTemplate.opsForValue().get("user:100"); //手动反序列化 User u = objectMapper.readValue(json, User.class); //打印数据 System.out.println("u = " + u); } }
这时实际存储在Redis中的是Value值如下:
{ "name": "虎哥", "age": 18 }
相对来说就不那么浪费内存了。