Адрес фактического центра электронной коммерции SpringBoot (30k+star):GitHub.com/macro-positive/…
Резюме
Spring Data Redis — это способ работы с Redis, предоставляемый фреймворком Spring. Недавно я разобрался с его использованием и решил некоторые трудности и подводные камни, возникающие в процессе использования. Надеюсь, он будет вам полезен. В этой статье рассказывается об установке Redis, использовании Spring Cache в сочетании с Redis, использовании пула соединений Redis и использовании RedisTemplate.
Установка Redis
Существует два метода установки: Linux и Windows.Поскольку версия для Windows — это только версия 3.2, рекомендуется использовать версию для Linux.Последняя стабильная версия — 5.0, которая также используется в этой статье.
Linux
Здесь мы используем метод установки в среде Docker.
- Загрузите Docker-образ Redis5.0;
docker pull redis:5.0
- Запустите контейнер Redis с помощью команды Docker;
docker run -p 6379:6379 --name redis \
-v /mydata/redis/data:/data \
-d redis:5.0 redis-server --appendonly yes
Windows
Друзья, которые хотят использовать версию для Windows, могут использовать следующие способы установки.
- Загрузите версию Redis для Windows, адрес загрузки:GitHub.com/Microsoft AR…
- После скачивания распакуйте его в указанную директорию;
- После ввода cmd в текущей адресной строке выполните команду запуска redis: redis-server.exe redis.windows.conf
Spring Cache управляет Redis
Введение в Spring кэш
Когда Spring Boot используется в сочетании с Redis в качестве кэша, проще всего использовать Spring Cache, используя его, нам не нужно знать различные операции Redis в Spring, а только через @Cacheable, @CachePut, @ CacheEvict, @ Такие аннотации, как EnableCaching, могут реализовать функцию кэширования.
Общие примечания
@EnableCaching
Включите функцию кеша, которая обычно помещается в класс запуска.
@Cacheable
Когда кеш существует, метод, использующий эту аннотацию, будет получать данные из кеша без выполнения метода, а когда кеш не существует, метод будет выполнен, а возвращенный результат будет сохранен в кеше.一般使用在查询方法上
, вы можете установить следующие свойства:
- значение: имя кеша (обязательно), укажите пространство имен кеша;
- ключ: используется для установки значения ключа кэша в пространстве имен, которое можно определить с помощью выражений SpEL;
- если: если условие выполнено, оно не будет закэшировано;
- условие: кешировать, если условие выполнено.
@CachePut
Метод, использующий эту аннотацию, будет сохранять возвращаемый результат в кеше при каждом выполнении.一般使用在新增方法上
, вы можете установить следующие свойства:
- значение: имя кеша (обязательно), укажите пространство имен кеша;
- ключ: используется для установки значения ключа кэша в пространстве имен, которое можно определить с помощью выражений SpEL;
- если: если условие выполнено, оно не будет закэшировано;
- условие: кешировать, если условие выполнено.
@CacheEvict
При выполнении метода, использующего эту аннотацию, указанный кеш будет очищен.一般使用在更新或删除方法上
, вы можете установить следующие свойства:
- значение: имя кеша (обязательно), укажите пространство имен кеша;
- ключ: используется для установки значения ключа кэша в пространстве имен, которое можно определить с помощью выражений SpEL;
- условие: кешировать, если условие выполнено.
Шаги для использования
- Добавьте зависимости проекта в pom.xml:
<!--redis依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- Измените файл конфигурации application.yml и добавьте конфигурацию подключения Redis;
spring:
redis:
host: 192.168.6.139 # Redis服务器地址
database: 0 # Redis数据库索引(默认为0)
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
timeout: 1000ms # 连接超时时间
- Добавьте аннотацию @EnableCaching в класс запуска, чтобы запустить функцию кэширования;
@EnableCaching
@SpringBootApplication
public class MallTinyApplication {
public static void main(String[] args) {
SpringApplication.run(MallTinyApplication.class, args);
}
}
- Далее используем соответствующие аннотации в классе PmsBrandServiceImpl для реализации функции кеширования, можно обнаружить, что аннотация @Cacheable используется в методе получения реквизитов бренда, а аннотация @CacheEvict — в методе модификации и удаления бренд;
/**
* PmsBrandService实现类
* Created by macro on 2019/4/19.
*/
@Service
public class PmsBrandServiceImpl implements PmsBrandService {
@Autowired
private PmsBrandMapper brandMapper;
@CacheEvict(value = RedisConfig.REDIS_KEY_DATABASE, key = "'pms:brand:'+#id")
@Override
public int update(Long id, PmsBrand brand) {
brand.setId(id);
return brandMapper.updateByPrimaryKeySelective(brand);
}
@CacheEvict(value = RedisConfig.REDIS_KEY_DATABASE, key = "'pms:brand:'+#id")
@Override
public int delete(Long id) {
return brandMapper.deleteByPrimaryKey(id);
}
@Cacheable(value = RedisConfig.REDIS_KEY_DATABASE, key = "'pms:brand:'+#id", unless = "#result==null")
@Override
public PmsBrand getItem(Long id) {
return brandMapper.selectByPrimaryKey(id);
}
}
- Мы можем вызвать интерфейс для получения сведений о бренде, чтобы проверить эффект.В настоящее время мы обнаруживаем, что данные, хранящиеся в Redis, немного искажены, а срок действия не установлен;
Хранить данные в формате JSON
На этом этапе мы подумаем о том, есть ли способ преобразовать данные, хранящиеся в Redis, в стандартный формат JSON, а затем мы можем установить определенное время истечения.Не устанавливая время истечения, легко сгенерировать множество ненужные кэшированные данные.
- Мы можем выполнить вышеуказанные требования, установив сериализатор в формате JSON для RedisTemplate и установив время ожидания, настроив RedisCacheConfiguration. В это время не забудьте удалить аннотацию @EnableCaching в классе запуска. Конкретный код RedisConfig класса конфигурации: следующее;
/**
* Redis配置类
* Created by macro on 2020/3/2.
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/**
* redis数据库自定义key
*/
public static final String REDIS_KEY_DATABASE="mall";
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<Object> serializer = redisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public RedisSerializer<Object> redisSerializer() {
//创建JSON序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置Redis缓存有效期为1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
}
- На этом этапе мы снова вызываем интерфейс для получения сведений о продукте для тестирования, и мы обнаружим, что данные стандартного формата JSON были закэшированы в Redis, а период ожидания установлен на 1 день.
Использовать пул соединений Redis
Клиент Redis версии SpringBoot 1.5.x реализован Jedis по умолчанию, а клиент по умолчанию в версии SpringBoot 2.x реализован Lettuce.Давайте сначала разберемся с клиентами Jedis и Lettuce.
Jedis vs Lettuce
Jedis напрямую подключается к службе Redis в реализации и не является потокобезопасным в многопоточной среде, если только пул соединений не используется для добавления физического подключения к каждому экземпляру RedisConnection.
Lettuce — это масштабируемый, потокобезопасный, полностью неблокирующий клиент Redis. Несколько потоков могут совместно использовать RedisConnection. Он использует структуру Netty NIO для эффективного управления несколькими подключениями, тем самым обеспечивая асинхронный и синхронный доступ к данным для создания неблокирующих реактивных приложений. .
Шаги для использования
- Измените application.yml, чтобы добавить конфигурацию пула соединений Lettuce для настройки количества потоков и времени ожидания блокировки;
spring:
redis:
lettuce:
pool:
max-active: 8 # 连接池最大连接数
max-idle: 8 # 连接池最大空闲连接数
min-idle: 0 # 连接池最小空闲连接数
max-wait: -1ms # 连接池最大阻塞等待时间,负值表示没有限制
- Поскольку пул соединений Redis не используется по умолчанию в SpringBoot 2.x, необходимо добавить зависимость commons-pool2 в pom.xml;
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- Если вы не добавите вышеуказанные зависимости, при запуске приложения возникнет следующая ошибка;
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
at org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration$LettucePoolingClientConfigurationBuilder.<init>(LettucePoolingClientConfiguration.java:84) ~[spring-data-redis-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration.builder(LettucePoolingClientConfiguration.java:48) ~[spring-data-redis-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$PoolBuilderFactory.createBuilder(LettuceConnectionConfiguration.java:149) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.createBuilder(LettuceConnectionConfiguration.java:107) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.getLettuceClientConfiguration(LettuceConnectionConfiguration.java:93) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.redisConnectionFactory(LettuceConnectionConfiguration.java:74) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$$EnhancerBySpringCGLIB$$5caa7e47.CGLIB$redisConnectionFactory$0(<generated>) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$$EnhancerBySpringCGLIB$$5caa7e47$$FastClassBySpringCGLIB$$b8ae2813.invoke(<generated>) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration$$EnhancerBySpringCGLIB$$5caa7e47.redisConnectionFactory(<generated>) ~[spring-boot-autoconfigure-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_91]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_91]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_91]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
... 111 common frames omitted
Работайте с Redis свободно
Spring Cache предоставляет нам удобный способ работы с кешем Redis, но у него также есть много ограничений. Например, что, если мы хотим установить срок действия кэшированного значения отдельно? Мы не хотим кэшировать возвращаемое значение метода, что, если мы хотим кэшировать промежуточное значение, сгенерированное в методе? На данный момент нам нужно использовать класс RedisTemplate.Далее поговорим о том, как свободно управлять кешем в Redis через RedisTemplate.
RedisService
Определите бизнес-класс операции Redis. В Redis есть несколько структур данных, таких как обычные структуры (объекты), структуры Hash, структуры Set и структуры List. В этом интерфейсе определены наиболее распространенные методы операций.
/**
* redis操作Service
* Created by macro on 2020/3/3.
*/
public interface RedisService {
/**
* 保存属性
*/
void set(String key, Object value, long time);
/**
* 保存属性
*/
void set(String key, Object value);
/**
* 获取属性
*/
Object get(String key);
/**
* 删除属性
*/
Boolean del(String key);
/**
* 批量删除属性
*/
Long del(List<String> keys);
/**
* 设置过期时间
*/
Boolean expire(String key, long time);
/**
* 获取过期时间
*/
Long getExpire(String key);
/**
* 判断是否有该属性
*/
Boolean hasKey(String key);
/**
* 按delta递增
*/
Long incr(String key, long delta);
/**
* 按delta递减
*/
Long decr(String key, long delta);
/**
* 获取Hash结构中的属性
*/
Object hGet(String key, String hashKey);
/**
* 向Hash结构中放入一个属性
*/
Boolean hSet(String key, String hashKey, Object value, long time);
/**
* 向Hash结构中放入一个属性
*/
void hSet(String key, String hashKey, Object value);
/**
* 直接获取整个Hash结构
*/
Map<Object, Object> hGetAll(String key);
/**
* 直接设置整个Hash结构
*/
Boolean hSetAll(String key, Map<String, Object> map, long time);
/**
* 直接设置整个Hash结构
*/
void hSetAll(String key, Map<String, Object> map);
/**
* 删除Hash结构中的属性
*/
void hDel(String key, Object... hashKey);
/**
* 判断Hash结构中是否有该属性
*/
Boolean hHasKey(String key, String hashKey);
/**
* Hash结构中属性递增
*/
Long hIncr(String key, String hashKey, Long delta);
/**
* Hash结构中属性递减
*/
Long hDecr(String key, String hashKey, Long delta);
/**
* 获取Set结构
*/
Set<Object> sMembers(String key);
/**
* 向Set结构中添加属性
*/
Long sAdd(String key, Object... values);
/**
* 向Set结构中添加属性
*/
Long sAdd(String key, long time, Object... values);
/**
* 是否为Set中的属性
*/
Boolean sIsMember(String key, Object value);
/**
* 获取Set结构的长度
*/
Long sSize(String key);
/**
* 删除Set结构中的属性
*/
Long sRemove(String key, Object... values);
/**
* 获取List结构中的属性
*/
List<Object> lRange(String key, long start, long end);
/**
* 获取List结构的长度
*/
Long lSize(String key);
/**
* 根据索引获取List中的属性
*/
Object lIndex(String key, long index);
/**
* 向List结构中添加属性
*/
Long lPush(String key, Object value);
/**
* 向List结构中添加属性
*/
Long lPush(String key, Object value, long time);
/**
* 向List结构中批量添加属性
*/
Long lPushAll(String key, Object... values);
/**
* 向List结构中批量添加属性
*/
Long lPushAll(String key, Long time, Object... values);
/**
* 从List结构中移除属性
*/
Long lRemove(String key, long count, Object value);
}
RedisServiceImpl
Класс реализации RedisService использует RedisTemplate для свободного управления кэшированными данными в Redis.
/**
* redis操作实现类
* Created by macro on 2020/3/3.
*/
@Service
public class RedisServiceImpl implements RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void set(String key, Object value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}
@Override
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
@Override
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public Boolean del(String key) {
return redisTemplate.delete(key);
}
@Override
public Long del(List<String> keys) {
return redisTemplate.delete(keys);
}
@Override
public Boolean expire(String key, long time) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
@Override
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
@Override
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
@Override
public Long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
@Override
public Long decr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, -delta);
}
@Override
public Object hGet(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
@Override
public Boolean hSet(String key, String hashKey, Object value, long time) {
redisTemplate.opsForHash().put(key, hashKey, value);
return expire(key, time);
}
@Override
public void hSet(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
@Override
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
@Override
public Boolean hSetAll(String key, Map<String, Object> map, long time) {
redisTemplate.opsForHash().putAll(key, map);
return expire(key, time);
}
@Override
public void hSetAll(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
}
@Override
public void hDel(String key, Object... hashKey) {
redisTemplate.opsForHash().delete(key, hashKey);
}
@Override
public Boolean hHasKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
@Override
public Long hIncr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, delta);
}
@Override
public Long hDecr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, -delta);
}
@Override
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
@Override
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
@Override
public Long sAdd(String key, long time, Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
expire(key, time);
return count;
}
@Override
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
@Override
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
@Override
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
@Override
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
@Override
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}
@Override
public Object lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
@Override
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
@Override
public Long lPush(String key, Object value, long time) {
Long index = redisTemplate.opsForList().rightPush(key, value);
expire(key, time);
return index;
}
@Override
public Long lPushAll(String key, Object... values) {
return redisTemplate.opsForList().rightPushAll(key, values);
}
@Override
public Long lPushAll(String key, Long time, Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
expire(key, time);
return count;
}
@Override
public Long lRemove(String key, long count, Object value) {
return redisTemplate.opsForList().remove(key, count, value);
}
}
RedisController
Чтобы протестировать контроллер операции кэширования в RedisService, вы можете вызвать его для тестирования.
/**
* Redis测试Controller
* Created by macro on 2020/3/3.
*/
@Api(tags = "RedisController", description = "Redis测试")
@Controller
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisService redisService;
@Autowired
private PmsBrandService brandService;
@ApiOperation("测试简单缓存")
@RequestMapping(value = "/simpleTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<PmsBrand> simpleTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
PmsBrand brand = brandList.get(0);
String key = "redis:simple:" + brand.getId();
redisService.set(key, brand);
PmsBrand cacheBrand = (PmsBrand) redisService.get(key);
return CommonResult.success(cacheBrand);
}
@ApiOperation("测试Hash结构的缓存")
@RequestMapping(value = "/hashTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<PmsBrand> hashTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
PmsBrand brand = brandList.get(0);
String key = "redis:hash:" + brand.getId();
Map<String, Object> value = BeanUtil.beanToMap(brand);
redisService.hSetAll(key, value);
Map<Object, Object> cacheValue = redisService.hGetAll(key);
PmsBrand cacheBrand = BeanUtil.mapToBean(cacheValue, PmsBrand.class, true);
return CommonResult.success(cacheBrand);
}
@ApiOperation("测试Set结构的缓存")
@RequestMapping(value = "/setTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<Set<Object>> setTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
String key = "redis:set:all";
redisService.sAdd(key, (Object[]) ArrayUtil.toArray(brandList, PmsBrand.class));
redisService.sRemove(key, brandList.get(0));
Set<Object> cachedBrandList = redisService.sMembers(key);
return CommonResult.success(cachedBrandList);
}
@ApiOperation("测试List结构的缓存")
@RequestMapping(value = "/listTest", method = RequestMethod.GET)
@ResponseBody
public CommonResult<List<Object>> listTest() {
List<PmsBrand> brandList = brandService.list(1, 5);
String key = "redis:list:all";
redisService.lPushAll(key, (Object[]) ArrayUtil.toArray(brandList, PmsBrand.class));
redisService.lRemove(key, 1, brandList.get(0));
List<Object> cachedBrandList = redisService.lRange(key, 0, 3);
return CommonResult.success(cachedBrandList);
}
}
Адрес исходного кода проекта
публика
проект торгового центраПолный набор учебных пособий сериализуется,Обратите внимание на публичный аккаунтПолучите это прямо сейчас.