Redis (5) ------Transactions, Jdies, SpringBoot integrated Redis
8. Affairs
-
A single Redis command guarantees atomicity, but a Redis transaction does not guarantee atomicity.
-
The essence of Redis transactions: a collection of commands
-
Each command in the transaction will be serialized and executed in order during execution, and no other commands are allowed to interfere.
-
Redis transactions have no concept of isolation level
-
Transaction characteristics:
- Disposable
- sequentiality
- exclusivity
-
All commands in the transaction are not executed when joining and will not start execution until submission (exec) is completed in one go
8.1 Redis transaction operation process
- Open transaction (multi)
- command to queue
- Execute transaction (exec)
- Cancel execution (discard)
8.1.1 Starting a transaction
# 开启事务
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 k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
# 事务执行
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
2) "k2"
3) "k1"
8.1.2 Cancel transaction
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> EXEC
# 当前未开启事务
(error) ERR EXEC without MULTI
# 被放弃事务中命令并未执行
127.0.0.1:6379> get k1
(nil)
8.1.3 Transaction errors (runtime errors, compile-time errors)
- Code syntax error (compile time exception)
# 编译时异常,是语法错误,所有命令都不生效
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> error k1
# 会报错但是不影响后续命令入队
(error) ERR unknown command `error`, with args beginning with: `k1`,
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
# 执行报错
(error) EXECABORT Transaction discarded because of previous errors.
# 其他命令并没有被执行
127.0.0.1:6379> get k1
(nil)
- Code logic errors (runtime exceptions)
# 运行时异常 其他所有指令正常执行
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> INCR k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
# 运行时报错
3) (error) ERR value is not an integer or out of range
# 其他命令正常执行
4) "v2"
8.2 Monitoring Watch
8.2.1 Pessimistic lock
- I am very pessimistic, thinking that problems will occur at any time and that no matter what I do, they will be locked.
8.2.2 Optimistic locking
- I am very optimistic and think that there will never be any problems or locks.
- When updating data, check whether anyone has modified the data during this period.
- Get the version and compare it when updating
8.2.3 Redis test monitoring test
- Normal execution successful
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
# 监视 money 对象
127.0.0.1:6379> watch money
OK
# 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功!
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
- Test multi-threaded modification of values. Use watch, which can be used as Redis's optimistic lock operation.
# 第一个客户端 先不执行exec 因为此时watch后的值还是原来的值
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
# 监视 money
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
# 第二个客户端
127.0.0.1:6379> set money 80
OK
# 第二个客户端执行完后 我再执行exec 就报错了
# 执行前,另一个线程,修改了money的值,会导致事务执行失败
127.0.0.1:6379> exec
(nil)
- If the modification fails, get the latest value
# 如果发现事务执行失败,就先解锁
127.0.0.1:6379> unwatch
OK
# 获取最新的值,再次监视,select version
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRby out 20
QUEUED
# 比对监视的值是否发生了变化,如果没有,执行成功,如果变量的版本变化了,执行失败
127.0.0.1:6379> exec
1) (integer) 60
2) (integer) 20
- Note: The lock will be automatically released after each submission and execution of exec, regardless of whether it is successful or not.
9、Jedis
Jedis
It is the java connection development tool officially recommended by Redis. It is a middleware that uses java to operate Redis.- If you want to use Java to operate Redis, you must be very familiar with Jedis!
9.1 Import the corresponding dependencies
<!--导入Jedis的包-->
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
</dependencies>
9.2 Coding Test
-
Open port 6379 in the firewall:
firewall-cmd --zone=public --add-port=6379/tcp --permanent
, and restart the firewall:systemctl restart firewalld.service
-
Remote connection to linux server needs modification
redis.config
# 后台运行
daemonize yes
protected-mode no
# bind是默认运行端口
注释掉 bind 127.0.0.1
-
After the modification is completed, restart redis,
./redis-cli -h linuxIP -p 6379
and if the ping is successful, the connection is successful. -
Connect to database, operate commands, disconnect
public class TestPing {
public static void main(String[] args) {
//第一步 new Jedis对象
Jedis jedis = new Jedis("linux服务器端口号",6379);
//Jedis所有的命令就是学习的Redis所有指令
//之前的指令就是方法
System.out.println(jedis.ping());
}
}
9.3 Commonly used APIs
- String
- List
- Set
- Hash
- Set
9.4 Jedis Affairs
public class TestPing {
public static void main(String[] args) {
//第一步 new Jedis对象
Jedis jedis = new Jedis("101.35.2.137",6379);
//Jedis所有的命令就是学习的Redis所有指令
//之前的指令就是方法 jedis.set()
System.out.println(jedis.ping());
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "zzz");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
// jedis.watch(result)
try {
multi.set("user1", result);
multi.set("user2", result);
// 执行事务
multi.exec();
}catch (Exception e){
// 放弃事务
multi.discard();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
// 关闭连接
jedis.close();
}
}
}
10. SpringBoot integrates Redis
10.1 Introduction
-
SpringBoot operation data: Spring-data, jpa, jdbc, mongodb, redis
-
SpringBoot data operations are all encapsulated in SpringData. SpringData is also a project as famous as SpringBoot
-
After the SpringBoot2.0 version, the original one used
jedis
was replaced bylettuce
-
Jedis:
The direct connection used is unsafe for multiple thread operations. If you want to avoid unsafe situations, usejedis pool
a connection pool. More like BIO mode -
Lettuce:
Using netty, instances can be shared among multiple threads, there is no thread insecurity, the number of threads can be reduced, and there is no need to open a connection pool. More like NIO mode
10.2 Integration testing
-
Steps: Create a new project, import dependencies, configure connections, and test
-
Create a new SpringBoot project and check the relevant dependencies
- Redis corresponds to dependencies. Click on the package containing lettuce. Continue clicking and there will be a package containing netty.
<!--操作redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
RedisAutoConfiguration
Source code analysis
@Bean
// 我们可以自己定义一个redisTemplate来替换这个默认的
// 当redisTemplate不存在时 默认模板就生效
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
// 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化的
// 两个泛型都是 Object, Object 的类型,使用时需要强制转换
//<String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
//由于String是redis中最常使用的类型,所以单独提出来了一 个bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
- Configuration files
application.propertise
, what attributes can be configured, are all written inRedisProperties
the class
# SpringBoot 所有的配置类,都有一个自动配置类
# 自动配置类会绑定一个properties配置文件
# RedisAutoConfiguration 绑定 RedisProperties
spring.redis.host=远程服务器IP
spring.redis.port=6379
- test
@SpringBootTest
class Redis02SpringbootApplicationTests {
//注入默认配置类
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型,api指令是一样的
// opsForValue 操作字符串 类似String
// opsForList 操作List 类似List
// opsForSet,opsForHash,opsForZSet,opsForGeo,opsForHyperLogLog
// 除了基本的操作,常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的CRUD
// 获取redis的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb(); 清除当前数据库
// connection.flushAll(); 清除全部数据库
//尽量不用中文 可能会被转义
redisTemplate.opsForValue().set("mykey","zzz");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
- The saving of all objects requires serialization! ! ! ! ! ! ! !
10.3 Writing RedisTemplate
- In the default RedisTemplate, serialization configuration of values
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
- In the default RedisTemplate, the default serialization method is JDK serialization, which will escape the string and can be serialized using josn.
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
- write your own
RedisTemplate
@Configuration
public class RedisConfig {
//编写自己的RedisTemplate,在AutoConfiguration中复制默认的函数,进行改写
// 这是一个固定模板,大家在企业中,拿去就可以直接使用
// 自己定义了一个 RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 为了自己开发方便,一般直接使用 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
template.setConnectionFactory(factory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
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;
}
}
- Write RedisUtil
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
// 在我们真实的分发中,或者在公司,一般都可以看到一个公司自己封装RedisUtil
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}