Linux(redis服務2(事務、Jedis、整合SpringBoot、對象的序列化))

Linux操作系統

Redis服務

(一)事務操作

1. 概述

redis單條命令是原子性操作,但事務不保證原子性。
redis事務的本質是:一組命令的集合!

  • 特點:
    事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
    事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。
2. 事務的執行

正常執行事務:

# 開啓事務
127.0.0.1:6379> multi
OK
# 命令入隊
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
# 執行事務
127.0.0.1:6379> exec
1) OK
2) OK
3) "v2"
# 執行完一個事務後,這個事務就停止了,再次開啓纔可以繼續下一個事務的操作!

放棄事務:
刷新一個事務中所有在排隊等待的指令,並且將連接狀態恢復到正常。

# 開啓事務
127.0.0.1:6379> multi
OK
# 命令入隊
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get k2
(nil)

注意:

  • 開啓事務後,出現錯誤的入隊命令,事務執行會報錯!所有命令都會執行失敗!
  • 當開啓事務後出現如:string不能執行 incr 加一的操作,執行事務後,不會影響事務的執行,這條命令會返回ERROR,但其他命令會執行成功,不影響操作!
3. 監控操作

WATCH:標記所有指定的key 被監視起來,在事務中有條件的執行(樂觀鎖)。
正常執行:

# 創建一個存有100元的key
127.0.0.1:6379> set money 100
OK
# 創建一個花費統計key
127.0.0.1:6379> set spend 0
OK
# 監控money
127.0.0.1:6379> watch money
OK
# 開啓事務
127.0.0.1:6379> multi
OK
# 花費50元
127.0.0.1:6379> decrby money 50
QUEUED
# 花費統計加50元
127.0.0.1:6379> incrby spend 50
QUEUED
# 執行事務
127.0.0.1:6379> exec
1) (integer) 50
2) (integer) 50
# 當事務執行完後,watch就會失效

多線程插隊,事務執行失敗,此時體現了watch當作樂觀鎖:
開啓兩個服務操作上面的事務,在第一個服務沒有執行事務之前,讓第二個服務插隊修改money的值,由於watch存在,所以再次提交事務就會失敗:

# 服務一
127.0.0.1:6379> watch money         # 再次監控
OK
127.0.0.1:6379> multi               # 開啓事務
OK
127.0.0.1:6379> decrby money 20     # 再次花費20元
QUEUED
127.0.0.1:6379> incrby spend 20     # 花費統計加20元
QUEUED

# 服務二
127.0.0.1:6379> get money           # 此時money還是50
"50"
127.0.0.1:6379> set money 200       # 在服務一執行事務之前將money設置爲200
OK

# 服務一執行事務:結果爲nil
127.0.0.1:6379> exec
(nil)

解決方法:unwatch

127.0.0.1:6379> unwatch           # 先unwatch:刷新一個事務中已被監視的所有key
OK
127.0.0.1:6379> multi             # 重新開啓事務
OK
...

(二)Jedis

1. 概述

Jedis是Redis官方推薦的Java連接開發工具。
這裏我們在windows環境下測試:首先保證win環境存在redis服務。
redis的zip包下載地址:https://github.com/MicrosoftArchive/redis/releases,下載解壓即可用!
在這裏插入圖片描述

2. Jedis的測試
  1. 創建項目,導入相關依賴:
<!-- jedis的包 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<!-- fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>
  1. 測試連接redis
import redis.clients.jedis.Jedis;

public class Test{
	public static void main(String[] args){
	    // 創建一個Jedis對象
		Jedis jedis = new Jedis("127.0.0.1",6379);
		// ping命令測試
		system.out.println(jedis.ping());
	} 
}

結果:
在這裏插入圖片描述
可以看到Jedis的方法都是redis的命令:
在這裏插入圖片描述

3. Jedis事務操作測試
  1. 正常事務的執行
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class Test2 {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        JSONObject json = new JSONObject();
        json.put("name", "zhangsan");
        json.put("age", "20");
        String string = json.toJSONString();
        // 開啓事務
        Transaction multi = jedis.multi();
        try {
            multi.set("user1",string);
            multi.set("user2",string);
            // 執行事務
            multi.exec();
        } catch (Exception e) {
            // 出現異常則放棄事務
            multi.discard();
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            // 關閉連接
            jedis.close();
        }
    }
}
/*
結果:
{"name":"zhangsan","age":"20"}
{"name":"zhangsan","age":"20"}
*/
  1. 異常事務
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class Test2 {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.flushDB();        // 刷新數據庫
        JSONObject json = new JSONObject();
        json.put("name", "zhangsan");
        json.put("age", "20");
        String string = json.toJSONString();
        // 開啓事務
        Transaction multi = jedis.multi();
        try {
            multi.set("user1",string);
            multi.set("user2",string);
            // 模擬error
            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();
        }
    }
}
/*
結果:
java.lang.ArithmeticException: / by zero
	at com.chen.Test2.main(Test2.java:23)
null
null
*/

(三) SpringBoot整合redis

1. 創建SpringBoot項目

在這裏插入圖片描述
引入redis後的pom.xml文件依賴:

<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

點進spring-boot-starter-data-redis發現:lettuce依賴。
在SpringBoot 2.x 版本後,jedis被替換爲lettuce
jedis:採用直連,多線程操作時,存在線性不安全,需使用jedis pool;
lettuce:採用netty,在多線程下不存在不安全。

2. 編寫application.properties文件
# 配置類
spring.redis.host=127.0.0.1
spring.redis.port=6379

SpringBoot的自動配置類:RedisAutoConfiguration
在這裏插入圖片描述
自動配置類綁定的properties類:RedisProperties

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
	/**
	 * Database index used by the connection factory.
	 */
	private int database = 0;    // 默認使用0號數據庫
	/**
	 * Connection URL. Overrides host, port, and password. User is ignored. Example:
	 * redis://user:[email protected]:6379
	 */
	private String url;
	/**
	 * Redis server host.
	 */
	private String host = "localhost";
	/**
	 * Login password of the redis server.
	 */
	private String password;
	/**
	 * Redis server port.
	 */
	private int port = 6379;    // 默認端口6379
	...
3. 測試

在test中注入RedisTemplate類,先來測試一下:

@SpringBootTest
class RedisSpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("k1","v1");
        redisTemplate.opsForValue().set("k2","哈哈哈 ");
        System.out.println(redisTemplate.opsForValue().get("k1"));
        System.out.println(redisTemplate.opsForValue().get("k2"));
    }
}

運行結果:
在這裏插入圖片描述
此類中其他操作:(事務、數據庫…)

@SpringBootTest
class RedisSpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
		// 事務操作
		redisTemplate.multi();
		redisTemplate.exec();
		// 數據庫的操作
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushDb();
        connection.flushAll();
    }
}

RedisTemplate 類的 opsForValue類似於string,還有其他的操作(hash、set、list…)
在這裏插入圖片描述

4. 對象序列化

編寫自己的序列化配置類:

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 {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 爲了方便開發使用<String,Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        // json序列化:用json解析任意的對象
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // string序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用string序列化
        template.setKeySerializer(stringRedisSerializer);
        // hash的key採用string序列化
        template.setHashKeySerializer(stringRedisSerializer);
        // value採用json序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value採用json序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

測試類:

@SpringBootTest
class RedisSpringbootApplicationTests {

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("k1","v1");
        redisTemplate.opsForValue().set("k2","哈哈哈 ");
        System.out.println(redisTemplate.opsForValue().get("k1"));
        System.out.println(redisTemplate.opsForValue().get("k2"));
    }

    @Test
    public void test(){
        User user = new User("zhangsan", 20);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
}

結果:
在這裏插入圖片描述

//下篇再見…謝謝
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章