第二十节:学习Redis缓存数据库实现增删改查(自学Spring boot 3.x的第五天)
这节记录下如何使用redis缓存数据库。
第一步:
先在服务器端安装redis,
下载地址:Releases · tporadowski/redis · GitHub。
第二步:
安装redis客户端可视化管理软件redisDesktopmanager
Redis Desktop Manager - Download
第三步:
设置redis缓存服务器的密码。
方式一(临时密码):打开redis-server.exe,输入config set requirepass 密码
方式二:永久密码,修改redis的conf文件,在conf中搜索requirepass。记得修改了以后重启(先停止再启动)redis的服务,不然不生效。
第四步:Redis Desktop Manager管理软件,测试是否能连接上Redis服务器。
第五步:开始配置Springboot中的redis
在application-local.yml文章中
spring:
datasource:
url: jdbc:mysql://localhost:3306/test123?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
# 连接池
hikari:
minimum-idle: 10
maximum-pool-size: 20
idle-timeout: 300000
max-lifetime: 1800000
connection-timeout: 30000
pool-name: SpringHikariCP
data:
redis:
host: 127.0.0.1
password: 123456
第六步:编写测试代码
@PostMapping
public void save(){
List<Student> studentList = studentService.list();
stringRedisTemplate.opsForValue().set("studentList", JSONUtil.toJsonStr(studentList));
}
@GetMapping
public List<Student> list(){
List<Student> list = JSONUtil.toList(stringRedisTemplate.opsForValue().get("studentList"),Student.class);
return list;
}
@PutMapping
public void update(){
List<Student> list = JSONUtil.toList(stringRedisTemplate.opsForValue().get("studentList"),Student.class);
Student student1 = new Student();
student1.setName("小王");
student1.setAge(18);
student1.setCreate_time(new Date());
list.add(student1);
stringRedisTemplate.opsForValue().set("studentList",JSONUtil.toJsonStr(list));
return ;
}
@DeleteMapping
public void delete(){
stringRedisTemplate.delete("studentList");
}
第七步:观察redis存储结果
上面记录了操作步骤。
那么接下来我们对代码进行分析。
一:redis是如何获取applocation.yml中的配置参数的?
Redis并不直接“获取”application.yml中的配置参数,而是Spring Boot框架在启动过程中读取这些配置参数,并据此配置Redis连接。应用程序通过Spring Boot提供的接口(如RedisTemplate)与Redis进行交互,而无需直接关心连接参数的配置。这种方式简化了应用程序与Redis的集成过程,提高了开发效率。
二:StringRedisTemplate类什么?
StringRedisTemplate类是Spring Data Redis提供的一个用于操作Redis的模板类。它专门用于处理Redis中的String类型数据,提供了一系列简化操作的方法,使开发者可以方便地进行数据的读取、写入和删除等操作,而无需关心底层Redis的连接和数据序列化等细节。
主要特点和用法
简化的操作方法:StringRedisTemplate封装了一系列简化的操作方法,如设置、获取、删除String类型数据等,开发者可以直接调用这些方法来进行操作,而无需关心Redis的底层实现。
与Spring集成:StringRedisTemplate是基于Spring框架提供的RedisTemplate实现的,因此可以与Spring应用程序无缝集成,通过依赖注入的方式进行使用。
支持事务:StringRedisTemplate支持在事务中进行操作,可以保证多个操作的原子性,确保数据的一致性。
支持序列化:StringRedisTemplate支持将Java对象序列化成Redis数据格式,以及将Redis数据反序列化成Java对象,方便开发者进行数据存取操作。
提供回调接口:StringRedisTemplate还提供了一些回调接口,如RedisCallback和SessionCallback,开发者可以通过这些接口来执行自定义的Redis操作,更灵活地控制Redis的使用。
构造方法
StringRedisTemplate提供了多种构造方法,通常开发者会通过配置Redis连接信息(如主机名、端口号等)来创建连接工厂,然后使用连接工厂来创建StringRedisTemplate实例。
常用操作方法
以下是一些常用的操作方法示例:
设置值:通过opsForValue().set(String key, String value)方法可以设置键值对。
获取值:通过opsForValue().get(String key)方法可以根据键获取对应的值。
删除值:通过delete(String key)方法可以删除指定的键值对。
追加字符串:通过opsForValue().append(String key, String value)方法可以向指定的键对应的字符串值的末尾追加值。
自增自减:通过opsForValue().increment(String key)和opsForValue().decrement(String key)方法可以实现字符串类型数据的自增和自减操作。
设置过期时间:在设置值时,可以指定过期时间,如opsForValue().set(String key, String value, long timeout, TimeUnit unit)方法。
注意事项
在使用StringRedisTemplate时,需要注意Redis服务器的连接信息配置,包括主机名、端口号、密码等。
对于数据类型为String的Redis操作,StringRedisTemplate提供了非常便捷的方法,但对于其他类型的数据(如Hash、List、Set等),则可能需要使用RedisTemplate或其他工具类进行操作。
在进行大量数据操作时,需要注意Redis的性能和内存使用情况,避免造成不必要的性能瓶颈或内存溢出等问题。
StringRedisTemplate 类源码
继承结构
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.data.redis.core;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
this.setKeySerializer(RedisSerializer.string());
this.setValueSerializer(RedisSerializer.string());
this.setHashKeySerializer(RedisSerializer.string());
this.setHashValueSerializer(RedisSerializer.string());
}
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
this.setConnectionFactory(connectionFactory);
this.afterPropertiesSet();
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}
源码分析:
StringRedisTemplate 继承自 RedisTemplate<String, String>,这意味着它是一个泛型类,其中键(Key)和值(Value)的类型都限定为 String。
StringRedisTemplate()这个构造函数通过调用 setKeySerializer、setValueSerializer、setHashKeySerializer 和 setHashValueSerializer 方法,将键、值、哈希键和哈希值的序列化器都设置为 RedisSerializer.string()。这意味着所有的键和值在存入 Redis 之前都会被转换成字符串,从 Redis 取出时也会从字符串转换回原来的类型(在这个场景下,由于本身就是字符串,所以实际上不需要转换)。
三:StringRedisTemplate和RedisTemplate区别在哪里
StringRedisTemplate和RedisTemplate在Spring框架中都是用于操作Redis数据库的模板类,但它们之间存在一些明显的区别,主要体现在以下几个方面:
1. 序列化策略
- StringRedisTemplate:采用的是String的序列化策略,即它将Java对象序列化为字符串形式存储在Redis中,同时也将Redis中的字符串反序列化为Java对象。这种方式简化了操作,因为字符串是Redis中最基本的数据类型,支持的操作简单高效。
- RedisTemplate:默认采用的是JDK的序列化策略(JdkSerializationRedisSerializer),即它将Java对象序列化为字节数组存储在Redis中。这种方式虽然可以处理任意类型的Java对象,但在Redis-cli中查看时可能会显示为乱码。不过,RedisTemplate的序列化策略是可以自定义的,常用的还有Jackson2JsonRedisSerializer等,可以将对象序列化为JSON字符串存储在Redis中。
2. 存储数据类型
- StringRedisTemplate:专注于处理Redis中的String类型数据,提供了一系列简化操作String类型数据的方法。
- RedisTemplate:是一个泛型类,可以对Redis中的任意类型数据进行操作,包括但不限于String、Hash、List、Set和Sorted Set等。它提供了丰富的API来支持这些数据类型的操作。
3. 使用范围
- StringRedisTemplate:由于其专注于String类型数据,因此适用于存储简单的键值对、缓存数据、计数器等场景。
- RedisTemplate:由于其泛型特性和丰富的API,适用于需要存储复杂对象或进行复杂查询的场景。如果你需要存取复杂的对象,并且不想做额外的处理,那么RedisTemplate可能是一个更好的选择。
4. 继承关系
- StringRedisTemplate:实际上是RedisTemplate的一个子类,它在继承RedisTemplate的基础上,将泛型类型指定为了String,从而限制了其只能处理String类型的键值对。
5. 示例
假设我们有一个User对象需要存储到Redis中:
- 使用StringRedisTemplate时,如果直接尝试存储User对象,会报错,因为StringRedisTemplate的set方法只接受String类型的键和值。我们需要先将User对象转换为String(例如通过JSON序列化),然后再存储。
- 使用RedisTemplate时,如果采用默认的JDK序列化策略,可以直接存储User对象,但在Redis-cli中查看时会是乱码。如果采用JSON序列化策略(如Jackson2JsonRedisSerializer),则可以将User对象序列化为JSON字符串后存储,这样在Redis-cli中查看时会是可读的JSON字符串。
综上所述,StringRedisTemplate和RedisTemplate在序列化策略、存储数据类型、使用范围、继承关系等方面存在明显的区别。在选择使用哪个模板类时,需要根据具体的应用场景和需求来决定。
四:RedisTemplate源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
private boolean enableTransactionSupport = false;
private boolean exposeConnection = false;
private boolean initialized = false;
private boolean enableDefaultSerializer = true;
@Nullable
private RedisSerializer<?> defaultSerializer;
@Nullable
private ClassLoader classLoader;
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
@Nullable
private ScriptExecutor<K> scriptExecutor;
private final BoundOperationsProxyFactory boundOperations = new BoundOperationsProxyFactory();
private final ValueOperations<K, V> valueOps = new DefaultValueOperations(this);
private final ListOperations<K, V> listOps = new DefaultListOperations(this);
private final SetOperations<K, V> setOps = new DefaultSetOperations(this);
private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations(this, ObjectHashMapper.getSharedInstance());
private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations(this);
private final GeoOperations<K, V> geoOps = new DefaultGeoOperations(this);
private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations(this);
private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations(this);
public RedisTemplate() {
}
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
}
}
if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
this.initialized = true;
}
public boolean isExposeConnection() {
return this.exposeConnection;
}
public void setExposeConnection(boolean exposeConnection) {
this.exposeConnection = exposeConnection;
}
public boolean isEnableDefaultSerializer() {
return this.enableDefaultSerializer;
}
public void setEnableDefaultSerializer(boolean enableDefaultSerializer) {
this.enableDefaultSerializer = enableDefaultSerializer;
}
public void setEnableTransactionSupport(boolean enableTransactionSupport) {
this.enableTransactionSupport = enableTransactionSupport;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Nullable
public RedisSerializer<?> getDefaultSerializer() {
return this.defaultSerializer;
}
public void setDefaultSerializer(RedisSerializer<?> serializer) {
this.defaultSerializer = serializer;
}
public void setKeySerializer(RedisSerializer<?> serializer) {
this.keySerializer = serializer;
}
public RedisSerializer<?> getKeySerializer() {
return this.keySerializer;
}
public void setValueSerializer(RedisSerializer<?> serializer) {
this.valueSerializer = serializer;
}
public RedisSerializer<?> getValueSerializer() {
return this.valueSerializer;
}
public RedisSerializer<?> getHashKeySerializer() {
return this.hashKeySerializer;
}
public void setHashKeySerializer(RedisSerializer<?> hashKeySerializer) {
this.hashKeySerializer = hashKeySerializer;
}
public RedisSerializer<?> getHashValueSerializer() {
return this.hashValueSerializer;
}
public void setHashValueSerializer(RedisSerializer<?> hashValueSerializer) {
this.hashValueSerializer = hashValueSerializer;
}
public RedisSerializer<String> getStringSerializer() {
return this.stringSerializer;
}
public void setStringSerializer(RedisSerializer<String> stringSerializer) {
this.stringSerializer = stringSerializer;
}
public void setScriptExecutor(ScriptExecutor<K> scriptExecutor) {
this.scriptExecutor = scriptExecutor;
}
@Nullable
public <T> T execute(RedisCallback<T> action) {
return this.execute(action, this.isExposeConnection());
}
@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {
return this.execute(action, exposeConnection, false);
}
@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(action, "Callback object must not be null");
RedisConnectionFactory factory = this.getRequiredConnectionFactory();
RedisConnection conn = RedisConnectionUtils.getConnection(factory, this.enableTransactionSupport);
Object var11;
try {
boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
RedisConnection connToUse = this.preProcessConnection(conn, existingConnection);
boolean pipelineStatus = connToUse.isPipelined();
if (pipeline && !pipelineStatus) {
connToUse.openPipeline();
}
RedisConnection connToExpose = exposeConnection ? connToUse : this.createRedisConnectionProxy(connToUse);
T result = action.doInRedis(connToExpose);
if (pipeline && !pipelineStatus) {
connToUse.closePipeline();
}
var11 = this.postProcessResult(result, connToUse, existingConnection);
} finally {
RedisConnectionUtils.releaseConnection(conn, factory);
}
return var11;
}
public <T> T execute(SessionCallback<T> session) {
Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(session, "Callback object must not be null");
RedisConnectionFactory factory = this.getRequiredConnectionFactory();
RedisConnectionUtils.bindConnection(factory, this.enableTransactionSupport);
Object var3;
try {
var3 = session.execute(this);
} finally {
RedisConnectionUtils.unbindConnection(factory);
}
return var3;
}
public List<Object> executePipelined(SessionCallback<?> session) {
return this.executePipelined(session, this.valueSerializer);
}
public List<Object> executePipelined(SessionCallback<?> session, @Nullable RedisSerializer<?> resultSerializer) {
Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(session, "Callback object must not be null");
RedisConnectionFactory factory = this.getRequiredConnectionFactory();
RedisConnectionUtils.bindConnection(factory, this.enableTransactionSupport);
List var4;
try {
var4 = (List)this.execute((connection) -> {
connection.openPipeline();
boolean pipelinedClosed = false;
List var7;
try {
Object result = this.executeSession(session);
if (result != null) {
throw new InvalidDataAccessApiUsageException("Callback cannot return a non-null value as it gets overwritten by the pipeline");
}
List<Object> closePipeline = connection.closePipeline();
pipelinedClosed = true;
var7 = this.deserializeMixedResults(closePipeline, resultSerializer, this.hashKeySerializer, this.hashValueSerializer);
} finally {
if (!pipelinedClosed) {
connection.closePipeline();
}
}
return var7;
});
} finally {
RedisConnectionUtils.unbindConnection(factory);
}
return var4;
}
public List<Object> executePipelined(RedisCallback<?> action) {
return this.executePipelined(action, this.valueSerializer);
}
public List<Object> executePipelined(RedisCallback<?> action, @Nullable RedisSerializer<?> resultSerializer) {
return (List)this.execute((connection) -> {
connection.openPipeline();
boolean pipelinedClosed = false;
List var7;
try {
Object result = action.doInRedis(connection);
if (result != null) {
throw new InvalidDataAccessApiUsageException("Callback cannot return a non-null value as it gets overwritten by the pipeline");
}
List<Object> closePipeline = connection.closePipeline();
pipelinedClosed = true;
var7 = this.deserializeMixedResults(closePipeline, resultSerializer, this.hashKeySerializer, this.hashValueSerializer);
} finally {
if (!pipelinedClosed) {
connection.closePipeline();
}
}
return var7;
});
}
public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
return this.scriptExecutor.execute(script, keys, args);
}
public <T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSerializer<T> resultSerializer, List<K> keys, Object... args) {
return this.scriptExecutor.execute(script, argsSerializer, resultSerializer, keys, args);
}
public <T extends Closeable> T executeWithStickyConnection(RedisCallback<T> callback) {
Assert.isTrue(this.initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(callback, "Callback object must not be null");
RedisConnectionFactory factory = this.getRequiredConnectionFactory();
RedisConnection connection = this.preProcessConnection(RedisConnectionUtils.doGetConnection(factory, true, false, false), false);
return (Closeable)callback.doInRedis(connection);
}
private Object executeSession(SessionCallback<?> session) {
return session.execute(this);
}
protected RedisConnection createRedisConnectionProxy(RedisConnection connection) {
Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(connection.getClass(), this.getClass().getClassLoader());
return (RedisConnection)Proxy.newProxyInstance(connection.getClass().getClassLoader(), ifcs, new CloseSuppressingInvocationHandler(connection));
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return connection;
}
@Nullable
protected <T> T postProcessResult(@Nullable T result, RedisConnection conn, boolean existingConnection) {
return result;
}
public Boolean copy(K source, K target, boolean replace) {
byte[] sourceKey = this.rawKey(source);
byte[] targetKey = this.rawKey(target);
return (Boolean)this.doWithKeys((connection) -> {
return connection.copy(sourceKey, targetKey, replace);
});
}
public Boolean hasKey(K key) {
byte[] rawKey = this.rawKey(key);
return (Boolean)this.doWithKeys((connection) -> {
return connection.exists(rawKey);
});
}
public Long countExistingKeys(Collection<K> keys) {
Assert.notNull(keys, "Keys must not be null");
byte[][] rawKeys = this.rawKeys(keys);
return (Long)this.doWithKeys((connection) -> {
return connection.exists(rawKeys);
});
}
public Boolean delete(K key) {
byte[] rawKey = this.rawKey(key);
Long result = (Long)this.doWithKeys((connection) -> {
return connection.del(new byte[][]{rawKey});
});
return result != null && result.intValue() == 1;
}
public Long delete(Collection<K> keys) {
if (CollectionUtils.isEmpty(keys)) {
return 0L;
} else {
byte[][] rawKeys = this.rawKeys(keys);
return (Long)this.doWithKeys((connection) -> {
return connection.del(rawKeys);
});
}
}
public Boolean unlink(K key) {
byte[] rawKey = this.rawKey(key);
Long result = (Long)this.doWithKeys((connection) -> {
return connection.unlink(new byte[][]{rawKey});
});
return result != null && result.intValue() == 1;
}
public Long unlink(Collection<K> keys) {
if (CollectionUtils.isEmpty(keys)) {
return 0L;
} else {
byte[][] rawKeys = this.rawKeys(keys);
return (Long)this.doWithKeys((connection) -> {
return connection.unlink(rawKeys);
});
}
}
public DataType type(K key) {
byte[] rawKey = this.rawKey(key);
return (DataType)this.doWithKeys((connection) -> {
return connection.type(rawKey);
});
}
public Set<K> keys(K pattern) {
byte[] rawKey = this.rawKey(pattern);
Set<byte[]> rawKeys = (Set)this.doWithKeys((connection) -> {
return connection.keys(rawKey);
});
return this.keySerializer != null ? SerializationUtils.deserialize(rawKeys, this.keySerializer) : rawKeys;
}
public Cursor<K> scan(ScanOptions options) {
Assert.notNull(options, "ScanOptions must not be null");
return (Cursor)this.executeWithStickyConnection((connection) -> {
return new ConvertingCursor(connection.scan(options), this::deserializeKey);
});
}
public K randomKey() {
byte[] rawKey = (byte[])this.doWithKeys(RedisKeyCommands::randomKey);
return this.deserializeKey(rawKey);
}
public void rename(K oldKey, K newKey) {
byte[] rawOldKey = this.rawKey(oldKey);
byte[] rawNewKey = this.rawKey(newKey);
this.doWithKeys((connection) -> {
connection.rename(rawOldKey, rawNewKey);
return null;
});
}
public Boolean renameIfAbsent(K oldKey, K newKey) {
byte[] rawOldKey = this.rawKey(oldKey);
byte[] rawNewKey = this.rawKey(newKey);
return (Boolean)this.doWithKeys((connection) -> {
return connection.renameNX(rawOldKey, rawNewKey);
});
}
public Boolean expire(K key, final long timeout, final TimeUnit unit) {
byte[] rawKey = this.rawKey(key);
long rawTimeout = TimeoutUtils.toMillis(timeout, unit);
return (Boolean)this.doWithKeys((connection) -> {
try {
return connection.pExpire(rawKey, rawTimeout);
} catch (Exception var8) {
return connection.expire(rawKey, TimeoutUtils.toSeconds(timeout, unit));
}
});
}
public Boolean expireAt(K key, final Date date) {
byte[] rawKey = this.rawKey(key);
return (Boolean)this.doWithKeys((connection) -> {
try {
return connection.pExpireAt(rawKey, date.getTime());
} catch (Exception var4) {
return connection.expireAt(rawKey, date.getTime() / 1000L);
}
});
}
public Boolean persist(K key) {
byte[] rawKey = this.rawKey(key);
return (Boolean)this.doWithKeys((connection) -> {
return connection.persist(rawKey);
});
}
public Long getExpire(K key) {
byte[] rawKey = this.rawKey(key);
return (Long)this.doWithKeys((connection) -> {
return connection.ttl(rawKey);
});
}
public Long getExpire(K key, TimeUnit timeUnit) {
byte[] rawKey = this.rawKey(key);
return (Long)this.doWithKeys((connection) -> {
try {
return connection.pTtl(rawKey, timeUnit);
} catch (Exception var4) {
return connection.ttl(rawKey, timeUnit);
}
});
}
public Boolean move(K key, final int dbIndex) {
byte[] rawKey = this.rawKey(key);
return (Boolean)this.doWithKeys((connection) -> {
return connection.move(rawKey, dbIndex);
});
}
public byte[] dump(K key) {
byte[] rawKey = this.rawKey(key);
return (byte[])this.doWithKeys((connection) -> {
return connection.dump(rawKey);
});
}
public void restore(K key, byte[] value, long timeToLive, TimeUnit unit, boolean replace) {
byte[] rawKey = this.rawKey(key);
long rawTimeout = TimeoutUtils.toMillis(timeToLive, unit);
this.doWithKeys((connection) -> {
connection.restore(rawKey, rawTimeout, value, replace);
return null;
});
}
@Nullable
private <T> T doWithKeys(Function<RedisKeyCommands, T> action) {
return this.execute((connection) -> {
return action.apply(connection.keyCommands());
}, true);
}
public List<V> sort(SortQuery<K> query) {
return this.sort(query, this.valueSerializer);
}
public <T> List<T> sort(SortQuery<K> query, @Nullable RedisSerializer<T> resultSerializer) {
byte[] rawKey = this.rawKey(query.getKey());
SortParameters params = QueryUtils.convertQuery(query, this.stringSerializer);
List<byte[]> vals = (List)this.doWithKeys((connection) -> {
return connection.sort(rawKey, params);
});
return SerializationUtils.deserialize(vals, resultSerializer);
}
public <T> List<T> sort(SortQuery<K> query, BulkMapper<T, V> bulkMapper) {
return this.sort(query, bulkMapper, this.valueSerializer);
}
public <T, S> List<T> sort(SortQuery<K> query, BulkMapper<T, S> bulkMapper, @Nullable RedisSerializer<S> resultSerializer) {
List<S> values = this.sort(query, resultSerializer);
if (values != null && !values.isEmpty()) {
int bulkSize = query.getGetPattern().size();
List<T> result = new ArrayList(values.size() / bulkSize + 1);
List<S> bulk = new ArrayList(bulkSize);
Iterator var8 = values.iterator();
while(var8.hasNext()) {
S s = var8.next();
bulk.add(s);
if (bulk.size() == bulkSize) {
result.add(bulkMapper.mapBulk(Collections.unmodifiableList(bulk)));
bulk = new ArrayList(bulkSize);
}
}
return result;
} else {
return Collections.emptyList();
}
}
public Long sort(SortQuery<K> query, K storeKey) {
byte[] rawStoreKey = this.rawKey(storeKey);
byte[] rawKey = this.rawKey(query.getKey());
SortParameters params = QueryUtils.convertQuery(query, this.stringSerializer);
return (Long)this.doWithKeys((connection) -> {
return connection.sort(rawKey, params, rawStoreKey);
});
}
public void watch(K key) {
byte[] rawKey = this.rawKey(key);
this.executeWithoutResult((connection) -> {
connection.watch(new byte[][]{rawKey});
});
}
public void watch(Collection<K> keys) {
byte[][] rawKeys = this.rawKeys(keys);
this.executeWithoutResult((connection) -> {
connection.watch(rawKeys);
});
}
public void unwatch() {
this.executeWithoutResult(RedisTxCommands::unwatch);
}
public void multi() {
this.executeWithoutResult(RedisTxCommands::multi);
}
public void discard() {
this.executeWithoutResult(RedisTxCommands::discard);
}
public List<Object> exec() {
List<Object> results = this.execRaw();
return this.getRequiredConnectionFactory().getConvertPipelineAndTxResults() ? this.deserializeMixedResults(results, this.valueSerializer, this.hashKeySerializer, this.hashValueSerializer) : results;
}
public List<Object> exec(RedisSerializer<?> valueSerializer) {
return this.deserializeMixedResults(this.execRaw(), valueSerializer, valueSerializer, valueSerializer);
}
protected List<Object> execRaw() {
List<Object> raw = (List)this.execute(RedisTxCommands::exec);
return raw == null ? Collections.emptyList() : raw;
}
public List<RedisClientInfo> getClientList() {
return (List)this.execute(RedisServerCommands::getClientList);
}
public void killClient(String host, int port) {
this.executeWithoutResult((connection) -> {
connection.killClient(host, port);
});
}
public void replicaOf(String host, int port) {
this.executeWithoutResult((connection) -> {
connection.replicaOf(host, port);
});
}
public void replicaOfNoOne() {
this.executeWithoutResult(RedisServerCommands::replicaOfNoOne);
}
public Long convertAndSend(String channel, Object message) {
Assert.hasText(channel, "a non-empty channel is required");
byte[] rawChannel = this.rawString(channel);
byte[] rawMessage = this.rawValue(message);
return (Long)this.execute((connection) -> {
return connection.publish(rawChannel, rawMessage);
}, true);
}
private void executeWithoutResult(Consumer<RedisConnection> action) {
this.execute((it) -> {
action.accept(it);
return null;
}, true);
}
public ClusterOperations<K, V> opsForCluster() {
return this.clusterOps;
}
public GeoOperations<K, V> opsForGeo() {
return this.geoOps;
}
public BoundGeoOperations<K, V> boundGeoOps(K key) {
return (BoundGeoOperations)this.boundOperations.createProxy(BoundGeoOperations.class, key, DataType.ZSET, this, RedisOperations::opsForGeo);
}
public <HK, HV> BoundHashOperations<K, HK, HV> boundHashOps(K key) {
return (BoundHashOperations)this.boundOperations.createProxy(BoundHashOperations.class, key, DataType.HASH, this, (it) -> {
return it.opsForHash();
});
}
public <HK, HV> HashOperations<K, HK, HV> opsForHash() {
return new DefaultHashOperations(this);
}
public HyperLogLogOperations<K, V> opsForHyperLogLog() {
return this.hllOps;
}
public ListOperations<K, V> opsForList() {
return this.listOps;
}
public BoundListOperations<K, V> boundListOps(K key) {
return (BoundListOperations)this.boundOperations.createProxy(BoundListOperations.class, key, DataType.LIST, this, RedisOperations::opsForList);
}
public BoundSetOperations<K, V> boundSetOps(K key) {
return (BoundSetOperations)this.boundOperations.createProxy(BoundSetOperations.class, key, DataType.SET, this, RedisOperations::opsForSet);
}
public SetOperations<K, V> opsForSet() {
return this.setOps;
}
public <HK, HV> StreamOperations<K, HK, HV> opsForStream() {
return this.streamOps;
}
public <HK, HV> StreamOperations<K, HK, HV> opsForStream(HashMapper<? super K, ? super HK, ? super HV> hashMapper) {
return new DefaultStreamOperations(this, hashMapper);
}
public <HK, HV> BoundStreamOperations<K, HK, HV> boundStreamOps(K key) {
return (BoundStreamOperations)this.boundOperations.createProxy(BoundStreamOperations.class, key, DataType.STREAM, this, (it) -> {
return this.opsForStream();
});
}
public BoundValueOperations<K, V> boundValueOps(K key) {
return (BoundValueOperations)this.boundOperations.createProxy(BoundValueOperations.class, key, DataType.STRING, this, RedisOperations::opsForValue);
}
public ValueOperations<K, V> opsForValue() {
return this.valueOps;
}
public BoundZSetOperations<K, V> boundZSetOps(K key) {
return (BoundZSetOperations)this.boundOperations.createProxy(BoundZSetOperations.class, key, DataType.ZSET, this, RedisOperations::opsForZSet);
}
public ZSetOperations<K, V> opsForZSet() {
return this.zSetOps;
}
private byte[] rawKey(Object key) {
Assert.notNull(key, "non null key required");
if (this.keySerializer == null && key instanceof byte[] bytes) {
return bytes;
} else {
return this.keySerializer.serialize(key);
}
}
private byte[] rawString(String key) {
return this.stringSerializer.serialize(key);
}
private byte[] rawValue(Object value) {
if (this.valueSerializer == null && value instanceof byte[] bytes) {
return bytes;
} else {
return this.valueSerializer.serialize(value);
}
}
private byte[][] rawKeys(Collection<K> keys) {
byte[][] rawKeys = new byte[keys.size()][];
int i = 0;
Object key;
for(Iterator var4 = keys.iterator(); var4.hasNext(); rawKeys[i++] = this.rawKey(key)) {
key = var4.next();
}
return rawKeys;
}
private K deserializeKey(byte[] value) {
return this.keySerializer != null ? this.keySerializer.deserialize(value) : value;
}
@Nullable
private List<Object> deserializeMixedResults(@Nullable List<Object> rawValues, @Nullable RedisSerializer valueSerializer, @Nullable RedisSerializer hashKeySerializer, @Nullable RedisSerializer hashValueSerializer) {
if (rawValues == null) {
return null;
} else {
List<Object> values = new ArrayList();
Iterator var6 = rawValues.iterator();
while(true) {
while(var6.hasNext()) {
Object rawValue = var6.next();
if (rawValue instanceof byte[]) {
byte[] bytes = (byte[])rawValue;
if (valueSerializer != null) {
values.add(valueSerializer.deserialize(bytes));
continue;
}
}
if (rawValue instanceof List) {
List list = (List)rawValue;
values.add(this.deserializeMixedResults(list, valueSerializer, hashKeySerializer, hashValueSerializer));
} else {
if (rawValue instanceof Set) {
Set set = (Set)rawValue;
if (!set.isEmpty()) {
values.add(this.deserializeSet(set, valueSerializer));
continue;
}
}
if (rawValue instanceof Map) {
Map map = (Map)rawValue;
if (!map.isEmpty() && map.values().iterator().next() instanceof byte[]) {
values.add(SerializationUtils.deserialize(map, hashKeySerializer, hashValueSerializer));
continue;
}
}
values.add(rawValue);
}
}
return values;
}
}
}
private Set<?> deserializeSet(Set rawSet, @Nullable RedisSerializer valueSerializer) {
if (rawSet.isEmpty()) {
return rawSet;
} else {
Object setValue = rawSet.iterator().next();
if (setValue instanceof byte[] && valueSerializer != null) {
return SerializationUtils.deserialize(rawSet, valueSerializer);
} else {
return setValue instanceof Tuple ? this.convertTupleValues(rawSet, valueSerializer) : rawSet;
}
}
}
private Set<ZSetOperations.TypedTuple<V>> convertTupleValues(Set<Tuple> rawValues, @Nullable RedisSerializer valueSerializer) {
Set<ZSetOperations.TypedTuple<V>> set = new LinkedHashSet(rawValues.size());
Tuple rawValue;
Object value;
for(Iterator var4 = rawValues.iterator(); var4.hasNext(); set.add(new DefaultTypedTuple(value, rawValue.getScore()))) {
rawValue = (Tuple)var4.next();
value = rawValue.getValue();
if (valueSerializer != null) {
value = valueSerializer.deserialize(rawValue.getValue());
}
}
return set;
}
}
继承结构
很明显StringRedisTemplate和RedisTemplate是父类和子类关系。
在上面源码里看到,StringRedisTemplate并无重写RedisTemplate的任何方法。所以拥有RedisTemplate的所有方法。
五:StringRedisTemplate的主要方法有哪些?怎么设置缓存过期时间?
StringRedisTemplate
是Spring Framework提供的一个用于操作Redis数据库中String类型数据的模板类。它基于RedisTemplate
实现,并专注于处理Redis中的String类型数据,提供了一系列简化操作的方法。以下是StringRedisTemplate
的一些常用方法讲解:
1. 字符串操作
设置和获取值
opsForValue().set(String key, String value)
:向Redis中存入一个字符串类型的键值对。opsForValue().get(Object key)
:根据键获取对应的值。
设置带有过期时间的值
opsForValue().set(String key, String value, long timeout, TimeUnit unit)
:向Redis中存入一个字符串类型的键值对,并设置过期时间。timeout
是过期时长,unit
是时间单位(如秒、分钟等)。
递增和递减
opsForValue().increment(String key)
:将存储在键中的数字值增加1。opsForValue().increment(String key, long delta)
:将存储在键中的数字值增加指定的增量值delta
。opsForValue().decrement(String key)
和opsForValue().decrement(String key, long delta)
:与递增操作类似,但用于减少数字值。
2. 哈希操作
opsForHash().put(String key, Object hashKey, Object value)
:向哈希表中添加一个字段。opsForHash().entries(String key)
:获取存储在哈希表中指定键的所有字段和值。
3. 列表操作
opsForList().leftPush(String key, String value)
:将一个或多个值插入到列表头部。opsForList().rightPush(String key, String value)
:将一个或多个值插入到列表尾部。opsForList().range(String key, long start, long end)
:通过索引区间返回列表中的一部分元素。
4. 集合操作
opsForSet().add(String key, String... values)
:向集合添加一个或多个成员。opsForSet().members(String key)
:返回集合中的所有成员。
5. 有序集合操作
opsForZSet().add(String key, double score, String value)
:将一个或多个成员及其分数加入到有序集合中。opsForZSet().range(String key, long start, long end)
:通过索引区间返回有序集合中指定区间的成员。
6. 其他常用方法
delete(String key, Object... keys)
:根据提供的键删除缓存中的数据。hasKey(String key)
:检查键是否存在。getExpire(String key, TimeUnit unit)
:获取键的剩余生存时间,并转换成指定的时间单位。
注意事项
- 使用
StringRedisTemplate
时,通常不需要额外配置序列化器,因为它默认使用String序列化策略。但如果需要存储复杂对象,可能需要自定义序列化器。 StringRedisTemplate
提供的方法非常丰富,这里只列举了一些常用方法。更多方法可以通过查看官方文档或源码来了解。
总的来说,StringRedisTemplate
是Spring Data Redis中一个非常实用的工具类,它简化了Redis的操作,使开发者能够更轻松地将Redis集成到Spring应用程序中。
六:opsForSet()和opsForZSet()有什么区别?
opsForSet()
和opsForZSet()
是RedisTemplate
类中提供的两个方法,它们分别用于操作Redis中的SET和ZSET(Sorted Set)数据结构。以下是这两个方法的主要区别:
1. 数据结构特性
- opsForSet():用于操作SET数据结构。SET是一个无序的、不包含重复元素的字符串集合。SET中的元素没有特定的顺序,且每个元素都是唯一的。
- opsForZSet():用于操作ZSET数据结构。ZSET是一个有序的、不包含重复元素的字符串集合,每个元素都会关联一个分数(score),Redis会根据这个分数对元素进行排序。
2. 支持的操作
- opsForSet():支持的操作包括添加元素(SADD)、删除元素(SREM)、判断元素是否存在(SISMEMBER)、获取集合中的所有元素(SMEMBERS)、获取集合大小(SCARD)等。SET主要用于存储不需要排序的、唯一的元素集合。
- opsForZSet():除了支持类似SET的添加(ZADD)、删除(ZREM)和查找(ZSCORE)操作外,还支持根据分数范围查询元素(ZRANGEBYSCORE、ZREVRANGEBYSCORE)、获取元素的排名(ZRANK、ZREVRANK)、计算分数范围内的元素数量(ZCOUNT)等高级功能。ZSET主要用于需要根据分数进行排序或统计的场景。
3. 排序与分数
- opsForSet():由于SET是无序的,因此不支持根据任何特定顺序对元素进行排序或检索。
- opsForZSet():ZSET中的元素会根据关联的分数进行排序。可以通过分数范围或排名来检索元素,这使得ZSET非常适合用于实现排行榜、评分系统等需要排序或统计的场景。
4. 性能考虑
- opsForSet():由于SET是无序的,且每个元素都是唯一的,因此其操作时间复杂度一般为O(1),即常数时间复杂度,这使得它在处理大量数据时具有较高的效率。
- opsForZSet():虽然ZSET提供了排序功能,但其操作时间复杂度一般为O(logN),其中N是集合中元素的数量。这意味着随着集合大小的增加,操作所需的时间也会相应增加,但仍然保持较高的效率。
综上所述,opsForSet()
和opsForZSet()
的主要区别在于它们操作的数据结构特性、支持的操作、排序与分数以及性能考虑等方面。在选择使用哪个方法时,应根据实际需求进行权衡和选择。