Jedis連接工具
什么是Jedis?
測試
-
在本地主機進行測試
1、打開 Redis 服務
<!--導入jedis的包--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.66</version> </dependency>
3、測試連接
package com.zxh; import redis.clients.jedis.Jedis; public class TestPing { public static void main(String[] args) { // 1、 new Jedis 對象即可 Jedis jedis = new Jedis("127.0.0.1",6379); // jedis 所有的命令就是我們之前學習的所有指令!所以之前的指令學習很重要! System.out.println(jedis.ping()); } }
輸出:
常用API
所有的api命令,就是我們對應的上面學習的指令,一個都沒有變化!
package com.zxh; import com.alibaba.fastjson.JSONObject; import redis.clients.jedis.Jedis; import redis.clients.jedis.Transaction; public class TestTx { public static void main(String[] args) { // 建立連接 Jedis jedis = new Jedis("127.0.0.1", 6379); jedis.flushDB(); // 清除當前數據庫 JSONObject jsonObject = new JSONObject(); jsonObject.put("username", "zxh"); jsonObject.put("age", 20); String result = jsonObject.toJSONString(); // 開啟事務 Transaction multi = jedis.multi(); try { // 命令入隊 multi.set("user1", result); multi.set("user2", result); int i = 1/0; // 制造異常 //執行事務 multi.exec(); } catch (Exception e) { // 拋出異常,取消事務 multi.discard(); //消事務 e.printStackTrace(); } finally { System.out.println(jedis.get("user1")); System.out.println(jedis.get("user2")); jedis.close(); // 關閉連接 } } }
結果:
SpringBoot整合
准備工作和源碼分析
簡介
SpringBoot 操作數據:spring-data => jpa jdbc mongodb redis!
SpringData 也是和 SpringBoot齊名的項目!
准備工作
1、創建SpringBoot項目
-
勾選支持項
2、依賴
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
源碼分析
進入 Redis 的依賴可以找到,底層使用的就是 Jedis,不過可以看到還有另外一個 lettuce
說明
Jedis:采用的是直連,所以在多個線程操作的話,是不安全的!如果想要避免不安全,使用 Jedis pool 連接池! 更像 BIO(Blocking I/O,阻塞IO)
lettuce:采用netty,實例可以在多個線程中進行共享,不存在線程不安全的情況!可以減少線程數據量,更像 NIO(Nonblocking I/O,非阻塞IO)
NIO和BIO解釋
Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網絡通信框架,對比於BIO(Blocking I/O,阻塞IO),他的並發性能得到了很大提高
兩張圖讓你了解BIO和NIO的區別:
這兩張圖可以看出,NIO的單線程能處理連接的數量比BIO要高出很多,而為什么單線程能處理更多的連接呢?原因就是圖二中出現的Selector
當一個連接建立之后,它有兩個步驟要做:
-
第一步是接收客戶端發過來的全部數據
-
第二步是服務端處理完請求之后返回response客戶端
NIO和BIO的區別主要是在第一步:
-
在BIO中,等待客戶端發數據這個過程是阻塞的,這樣就造成了一個線程只能處理一個請求的情況,而機器能支持的最大線程數是有限的,這就是為什么BIO不能支持高並發的原因。
-
而NIO中,當一個Socket 建立之后,Thread 並不會阻塞接收這個 Socket,而是將這個請求交給 Selector,Selector 會不斷的去遍歷所有的 Socket,一旦有一個Socket 建立完成,他會通知Thread,然后 Thread 處理完數據后再返回給客戶端——這個過程是不阻塞的,這樣就能讓一個 Thread 處理更多的請求了。
RedisAutoConfiguration自動配置源碼分析
1、在SpringBoot的自動配置文件中,搜索redis可以找到對應的 Redis自動配置類
2、RedisAutoConfiguration類的源碼
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(RedisOperations.class) // 對應的引用配置文件 RedisProperties.class,所以說redis所有的配置可以在這里查看 @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { // 默認的是 RedisTemplate,他沒有過多的設置,其中的 Redis對象都是還需要序列化的! // 兩個泛型都是Object類型,我們之后使用需要強轉 <String, Object> // 所以@ConditionalOnMissingBean 表示我們可以自定義 RedisTemplate 來使用,那樣會更加方便 @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } // StringRedisTemplate,由於string類型是reids中最常用的類型,所以單獨提出來,方便開發人員調用 @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
配置文件
- 可以在配置文件 以 spring.redis.xxx 來進行配置
兩個API使用哪個?
-
哪個好已經在上面說過了,lettuce支持高並發
-
可以看到 源碼中,兩個方法都傳入了RedisConnectionFactory對象
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
進入這個接口可以看到有兩個實現類
所以在配置文件中配置時需要注意:SpringBoot 2.x以后 默認使用的lettuce,不要傻傻的配置到jedis了
測試
1、導入依賴
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
# 配置 redis,不過默認配置就是本機,端口也是6379,這里為了看的清除
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、測試連接
-
在測試類中進行測試,可以直接注入 RedisTemplate 對象
package com.zxh; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @SpringBootTest class Redis02SpringbootApplicationTests { @Autowired RedisTemplate redisTemplate; @Test void contextLoads() { RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory(); RedisConnection connection = connectionFactory.getConnection(); System.out.println(connection.ping()); } }
結果:
package com.zxh; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @SpringBootTest class Redis02SpringbootApplicationTests { @Autowired RedisTemplate redisTemplate; @Test void contextLoads() { /* redisTemplate:操作不同的數據類型,api和之前的命令都是一樣的 opsForValue():操作字符串,類型String opsForSet opsForHash opsForZSet opsForGeo opsForHyperLogLog */ // 除了進本的操作,我們常用的方法都可以直接通過redisTemplate操作,比如事務,和基本的 CRUD // 獲取redis的連接對象 // RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory(); // RedisConnection connection = connectionFactory.getConnection(); // System.out.println(connection.ping()); redisTemplate.opsForValue().set("name", "zxh"); System.out.println(redisTemplate.opsForValue().get("name")); } }
結果:
自定義RestTemplate
為什么要自定義?
-
首先RestTemplate 它沒有過多的配置
-
默認的RestTemplate 使用的是jdk序列化,會有一些問題
關於序列化
-
我們基於上面的測試,試一下存入的是中文的情況
@SpringBootTest class Redis02SpringbootApplicationTests { @Autowired RedisTemplate redisTemplate; @Test void contextLoads() { redisTemplate.opsForValue().set("name", "憤怒的張迅豪"); System.out.println(redisTemplate.opsForValue().get("name")); } }
結果:
2、使用客戶端連接,發現已經被轉義過的
源碼分析的時候就說過了,所有的reids對象都需要進行序列化,而我們默認使用的RestTemplate的序列化,所以說是有問題的
3、源碼分析
我們可以看到源碼中,創建了默認的RedisTemplate
類
點進去可以看到,序列化設置都是null
在構造器中,可以看到默認的序列化方式
<!-- jackson--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.2</version> </dependency>
package com.zxh.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; 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.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration // 配置類 public class RedisConfig { // 自定義RedisTemplate @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){ // 為了開發的方便,一般直接使用 <String, Object> RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // redis連接的線程安全工廠,源碼也是這樣配置的 // Json序列化配置 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); //解決jackson2無法序列化LocalDateTime的問題,這里擴展一個LocalDateTime類型,它是日期類型對象 jdk1.8出的(並且這個類是不可變的和線程安全的,可以研究一下它的API),當然還需要對這個對象進行json格式的轉換,如下圖: om.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS); om.registerModule(new JavaTimeModule()); jackson2JsonRedisSerializer.setObjectMapper(om); // String 的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
如果不配合使用,LocalDateTime類型放到Redis數據庫時,會變得很長一段,很難維護。
測試
控制台沒問題
客戶端連接,命令行也可以顯示
最后:在我們真實的開發中,或者你們在公司,一般都可以看到一個公司自己封裝RedisUtil