前些时间张同学问怎么在SpringBoot里整合Redis…,由于很久后才看到消息,于是…
我:你用Redis是想存储什么数据,整出来没
张同学:整出来了,我存key-value的,存用户token信息。
于是我想了解一下他是怎么整合Redis的
我:你咋整合的,给我说道说道...
张同学:就用RedisTemplate啊,哪里需要缓存就哪里添加一下,很简单。
看到这里我…,Spring为键值对的存储提供了一套标准接口,各个类库只要对这些标准接口做实现,那么就可以直接使用org.springframework.cache.annotation
包下的注解做缓存了,避免了代码入侵。
我:怎么不用@Cacheable这套注解...redis有整合,可以直接用上
我:既省事又没有代码入侵,总不能哪里用缓存我就手写一下查存一下数据吧...
张同学:。。。我都不知道有这些标准,现在框架都搭好了。
太懒了啊…
自己手动搭了一套SpringBoot
+ Redis
+ SpringCache
的demo,考虑到序列化,我打算用上Protostuff,它的特点是:基于Protobuf、高效的编解码性能、以及和其他序列化框架相比Protostuff
在序列化后字节码流更小,更易于传输及存储。
接下来进入正文…
jdk8, SpringBoot 2.1.9.RELEASE
本文所用源码已经上传github。
<!-- apache 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Protostuff 序列化&反序列化工具 -->
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.6.0</version>
</dependency>
spring:
cache:
type: redis
redis:
time-to-live: ${REDIS_CACHE_TTL:1800000}
redis:
host: ${REDIS_CACHE_HOST:www.yorozuyas.com}
port: ${REDIS_CACHE_PORT:6379}
password: ${REDIS_CACHE_PASSWORD:password}
这里只做了简单的配置,其他相关配置自行查看源码或相关资料进行更改。
透过源码,可以发现每个序列化器都是继承自org.springframework.data.redis.serializer.RedisSerializer
接口
package org.springframework.data.redis.serializer;
import org.springframework.lang.Nullable;
...
public interface RedisSerializer<T> {
/**
* Serialize the given object to binary data.
*/
@Nullable
byte[] serialize(@Nullable T t) throws SerializationException;
/**
* Deserialize an object from the given binary data.
*/
@Nullable
T deserialize(@Nullable byte[] bytes) throws SerializationException;
...
}
因此只需要自定义一个继承自该接口的类,并实现serialize
、deserialize
即可。
package com.yorozuyas.demo.cache;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 使用Protostuff对value进行编解码。
*/
public final class ProtostuffRedisSerializer implements RedisSerializer<Object> {
final Schema<CacheValueWrapper> schema = RuntimeSchema.getSchema( CacheValueWrapper.class );
@Override
public byte[] serialize( Object cvw ) throws SerializationException {
try {
return encode( cvw );
}
catch ( RuntimeException e ) {
throw new SerializationException( "Could not encode CacheValueWrapper to Protostuff: "
+ e.getMessage(), e );
}
}
@Override
public Object deserialize( byte[] bytes ) throws SerializationException {
try {
return decode( bytes );
}
catch ( RuntimeException e ) {
throw new SerializationException( "Could not decode Protostuff to CacheValueWrapper: "
+ e.getMessage(), e );
}
}
// do serialize
public byte[] encode( Object value ) {
final LinkedBuffer buffer = LinkedBuffer.allocate();
return ProtostuffIOUtil.toByteArray( new CacheValueWrapper( value ), schema, buffer );
}
// do deserialize
public Object decode( byte[] bytes ) {
CacheValueWrapper wrapper = new CacheValueWrapper();
ProtostuffIOUtil.mergeFrom( bytes, wrapper, schema );
return wrapper.getData();
}
@AllArgsConstructor
@NoArgsConstructor
public static class CacheValueWrapper {
@Getter
private Object data;
}
}
在源码org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
这个配置类中,可以看到注入了一个类型为RedisCacheManager
的Bean
,RedisCacheManager
在创建过程中,调用determineConfiguration
方法,并判断如果已经存在
org.springframework.data.redis.cache.RedisCacheConfiguration
这个Bean
,则直接返回,否则创建一个默认的org.springframework.data.redis.cache.RedisCacheConfiguration
交给RedisCacheManager
.
package org.springframework.boot.autoconfigure.cache;
...
@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
private final org.springframework.data.redis.cache.RedisCacheConfiguration redisCacheConfiguration;
RedisCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
return this.customizerInvoker.customize(builder.build());
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
ClassLoader classLoader) {
if (this.redisCacheConfiguration != null) {
return this.redisCacheConfiguration;
}
Redis redisProperties = this.cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
config = config.serializeValuesWith(
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
由于RedisCacheManager
负责创建和管理各个RedisCache
,并为各个RedisCache
提供默认配置,包含序列化器,过期时间等。
package org.springframework.data.redis.cache;
...
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
...
/**
* Configuration hook for creating {@link RedisCache} with given name and {@code cacheConfig}.
*/
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
...
}
因此我们只需要自定义一个org.springframework.data.redis.cache.RedisCacheConfiguration
的Bean
并交由Spring容器即可,后续在启动过程中,RedisCacheManager
在创建的时候,会拿到我们注入到Spring中的RedisCacheConfiguration
。
package com.yorozuyas.demo.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.cache.CacheProperties.Redis;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
@Configuration
@AutoConfigureAfter(CacheAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis", matchIfMissing = false)
public class RedisCacheAutoConfiguration {
@Autowired
private CacheProperties cacheProperties;
/**
* Custom {@link org.springframework.data.redis.cache.RedisCacheConfiguration}
*
*
* @see org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
*/
@Bean
public RedisCacheConfiguration determineConfiguration() {
final Redis redisProperties = cacheProperties.getRedis();
// Only open TTL, others close.
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.disableKeyPrefix()
// here, add custom ProtostuffRedisSerializer for value.
.serializeValuesWith( SerializationPair.fromSerializer( new ProtostuffRedisSerializer() ) );
if ( redisProperties.getTimeToLive() != null ) {
config = config.entryTtl( redisProperties.getTimeToLive() );
}
return config;
}
}
通常我们在买东西的时候,都需要一个明确的收件地址,而一个用户可以有多个收货地址。
No | field | description | data type | not null |
---|---|---|---|---|
1 | id | 主键 | bigint | y |
2 | uid | 用户唯一标识 | varchar | y |
3 | name | 收货人 | varchar | y |
4 | address | 收货地址 | varchar | y |
5 | mobile | 联系方式 | bigint | y |
DROP TABLE IF EXISTS tb_deliver_address CASCADE;
CREATE TABLE tb_deliver_address (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键id',
uid varchar(32) NOT NULL COMMENT '用户唯一标识',
name varchar(24) NOT NULL COMMENT '收货人',
address varchar(200) NOT NULL COMMENT '收货地址',
mobile BIGINT NOT NULL COMMENT '联系方式',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO tb_deliver_address(uid, name, address, mobile) VALUES ('zhangsan', '我的姐姐', '湖北省武汉市江汉区发展大道185号', 12345678910);
INSERT INTO tb_deliver_address(uid, name, address, mobile) VALUES ('zhangsan', '我的妹妹', '安徽省合肥市经济技术开发区芙蓉路678号', 12345678911);
INSERT INTO tb_deliver_address(uid, name, address, mobile) VALUES ('zhangsan', '我的弟弟', '浙江省杭州市滨江区江南大道龙湖滨江天街', 12345678912);
INSERT INTO tb_deliver_address(uid, name, address, mobile) VALUES ('zhangsan', '张三', '广东省佛山市顺德区大良迎宾路碧桂园·钻石湾', 12345678913);
package com.yorozuyas.demo.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class DeliverAddress {
private Long id;
private String uid;
private String name;
private String address;
private Long mobile;
}
package com.yorozuyas.demo.controller;
...
import com.yorozuyas.demo.controller.base.BaseResponseEntity;
import com.yorozuyas.demo.enums.Code;
import com.yorozuyas.demo.model.DeliverAddress;
import com.yorozuyas.demo.service.Protobuf2RedisService;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
@RequestMapping(value = "/address")
public class Protobuf2RedisController {
public static final String URL_NEW_ADDRESS = "/new";
public static final String URL_FETCH_ADDRESS = "/fetch";
public static final String URL_MODIFY_ADDRESS = "/modify";
public static final String URL_EVICT_ADDRESS = "/evict/{id}";
@Autowired
private Protobuf2RedisService protobuf2RedisService;
...
private void checkIn( DeliverAddress addressInfo ) {
if ( StringUtils.isNullOrEmpty( addressInfo.getUid() ) )
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'uid' must not be null.", null, null,
StandardCharsets.UTF_8 );
if ( StringUtils.isNullOrEmpty( addressInfo.getName() ) )
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'name' must not be null.", null, null,
StandardCharsets.UTF_8 );
if ( StringUtils.isNullOrEmpty( addressInfo.getAddress() ) )
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'address' must not be null.", null, null,
StandardCharsets.UTF_8 );
if ( addressInfo.getMobile() == null )
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'mobile' must not be null.", null, null,
StandardCharsets.UTF_8 );
}
}
##################################
# 新增地址
##################################
POST http://>/-path>/address/new
@PostMapping(value = URL_NEW_ADDRESS, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<BaseResponseEntity<String>> newAddress(
@RequestBody DeliverAddress addressInfo ) {
log.info( "Request-Method: POST, Request-Path: /new, addressInfo: {}", addressInfo.toString() );
checkIn( addressInfo );
protobuf2RedisService.newAddress( addressInfo );
BaseResponseEntity.BaseResponseEntityBuilder<String> builder = BaseResponseEntity.builder();
return ResponseEntity.ok()
.body( builder.code( Code.OK.getCode() )
.data( "Address added successfully" )
.build() );
};
##################################
# 查询收货地址
##################################
GET http://>/-path>/address/fetch
@GetMapping(value = URL_FETCH_ADDRESS, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<BaseResponseEntity<?>> fetchAddress(
@RequestParam(value = "uid", required = false) String uid ) {
log.info( "Request-Method: GET, Request-Path: /fetch, uid: {}", uid );
// check parameter must not be null
if ( StringUtils.isNullOrEmpty( uid ) ) {
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'uid' must not be null.", null, null,
StandardCharsets.UTF_8 );
}
List<DeliverAddress> data = protobuf2RedisService.fetchAddress( uid );
BaseResponseEntity.BaseResponseEntityBuilder<Object> entityBuilder = BaseResponseEntity.builder()
.code( Code.OK.getCode() );
if ( data.isEmpty() ) {
return ResponseEntity.ok()
.body( entityBuilder.data( "No result" ).build() );
}
return ResponseEntity.ok()
.body( entityBuilder.data( data ).build() );
}
##################################
# 修改收货地址
##################################
POST http://>/-path>/address/modify
@PostMapping(value = URL_MODIFY_ADDRESS, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<BaseResponseEntity<String>> modifyAddress(
@RequestBody DeliverAddress addressInfo ) {
log.info( "Request-Method: POST, Request-Path: /modify, addressInfo: {}", addressInfo.toString() );
// check parameter must not be null
if ( addressInfo.getId() == null )
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'id' must not be null.", null, null,
StandardCharsets.UTF_8 );
checkIn( addressInfo );
protobuf2RedisService.modifyAddress( addressInfo );
BaseResponseEntity.BaseResponseEntityBuilder<String> builder = BaseResponseEntity.builder();
return ResponseEntity.ok()
.body( builder.code( Code.OK.getCode() )
.data( "Address modified successfully" )
.build() );
}
##################################
# 删除收货地址
##################################
DELETE http://>/-path>/address/evict/{id}
@DeleteMapping(value = URL_EVICT_ADDRESS, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<BaseResponseEntity<String>> evictAddress(
@PathVariable(value = "id", required = false) Long id,
@RequestParam(value = "uid", required = false) String uid ) {
log.info( "Request-Method: DELETE, Request-Path: /evict, id: {}, uid: {}", id, uid );
// check parameter must not be null
if ( id == null )
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'id' must not be null.", null, null,
StandardCharsets.UTF_8 );
if ( StringUtils.isNullOrEmpty( uid ) )
throw HttpClientErrorException.create( HttpStatus.BAD_REQUEST, "'uid' must not be null.", null, null,
StandardCharsets.UTF_8 );
protobuf2RedisService.evictAddress( id, uid );
BaseResponseEntity.BaseResponseEntityBuilder<String> builder = BaseResponseEntity.builder();
return ResponseEntity.ok()
.body( builder.code( Code.OK.getCode() )
.data( "Address deleted successfully" )
.build() );
}
package com.yorozuyas.demo.dao.impl;
...
import com.yorozuyas.demo.dao.Protobuf2RedisDAO;
import com.yorozuyas.demo.model.DeliverAddress;
@Component
public class Protobuf2RedisDAOImpl implements Protobuf2RedisDAO {
/**
* name space
*/
private static final String NEW_ADDRESS = "com.yorozuyas.demo.dao.Protobuf2RedisMapper.newAddress";
private static final String FETCH_ADDRESS = "com.yorozuyas.demo.dao.Protobuf2RedisMapper.fetchAddress";
private static final String MODIFY_ADDRESS = "com.yorozuyas.demo.dao.Protobuf2RedisMapper.modifyAddress";
private static final String EVICT_ADDRESS = "com.yorozuyas.demo.dao.Protobuf2RedisMapper.evictAddress";
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
@Override
@Cacheable(cacheNames = "address", key = "'address:' + #addressInfo.getUid()", unless = "#result.isEmpty()")
public List<DeliverAddress> newAddress( DeliverAddress addressInfo ) {
sqlSessionTemplate.insert( NEW_ADDRESS, addressInfo );
return sqlSessionTemplate.selectList( FETCH_ADDRESS, addressInfo.getUid() );
}
@Override
@Cacheable(cacheNames = "address", key = "'address:' + #uid", unless = "#result.isEmpty()")
public List<DeliverAddress> fetchAddress( String uid ) {
return sqlSessionTemplate.selectList( FETCH_ADDRESS, uid );
}
@Override
@CachePut(cacheNames = "address", key = "'address:' + #addressInfo.getUid()", unless = "#result.isEmpty()")
public List<DeliverAddress> modifyAddress( DeliverAddress addressInfo ) {
sqlSessionTemplate.update( MODIFY_ADDRESS, addressInfo );
return sqlSessionTemplate.selectList( FETCH_ADDRESS, addressInfo.getUid() );
}
@Override
@CacheEvict(cacheNames = "address", key = "'address:' + #uid")
public int evictAddress( Long id, String uid ) {
final Map<String, Object> map = new HashMap<String, Object>( 2 );
map.put( "id", id );
map.put( "uid", uid );
return sqlSessionTemplate.delete( EVICT_ADDRESS, map );
}
}
运行SpringBoot。
查看控制台输出信息,输出如下:
2020-07-04 19:43:45.752 14548 --- [ioEventLoop-4-1] DEBUG io.lettuce.core.RedisClient - Connecting to Redis at www.yorozuyas.com:6379: Success
2020-07-04 19:43:45.754 14548 --- [ioEventLoop-4-1] DEBUG io.lettuce.core.RedisChannelHandler - dispatching command AsyncCommand [type=AUTH, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.AsyncCommand]
2020-07-04 19:43:45.755 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=AUTH, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.AsyncCommand]
2020-07-04 19:43:45.756 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] write(ctx, AsyncCommand [type=AUTH, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.AsyncCommand], promise)
2020-07-04 19:43:45.759 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] writing command AsyncCommand [type=AUTH, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.AsyncCommand]
2020-07-04 19:43:45.759 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] Sent: *2
$4
AUTH
$6
password
2020-07-04 19:43:45.762 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() done
2020-07-04 19:43:45.762 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.ConnectionWatchdog - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, last known addr=www.yorozuyas.com/121.36.211.56:6379] userEventTriggered(ctx, io.lettuce.core.ConnectionEvents$Activated@8ff7c65)
2020-07-04 19:43:45.762 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.ConnectionWatchdog - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, last known addr=www.yorozuyas.com/121.36.211.56:6379] userEventTriggered(ctx, io.lettuce.core.ConnectionEvents$Activated@8ff7c65)
2020-07-04 19:43:45.769 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Received: 5 bytes, 1 commands in the stack
2020-07-04 19:43:45.769 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Buffer: +OK
2020-07-04 19:43:45.769 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Stack contains: 1 commands
2020-07-04 19:43:45.769 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decode AsyncCommand [type=AUTH, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.AsyncCommand]
2020-07-04 19:43:45.772 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decoded AsyncCommand [type=AUTH, output=StatusOutput [output=OK, error='null'], commandType=io.lettuce.core.protocol.AsyncCommand], empty stack: true
2020-07-04 19:43:45.774 14548 --- [nio-8080-exec-2] DEBUG io.lettuce.core.RedisChannelHandler - dispatching command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 19:43:45.774 14548 --- [nio-8080-exec-2] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 19:43:45.775 14548 --- [nio-8080-exec-2] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() done
2020-07-04 19:43:45.775 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] write(ctx, AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise)
2020-07-04 19:43:45.776 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] writing command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 19:43:45.776 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] Sent: *2
$3
GET
$16
address:zhangsan
2020-07-04 19:43:45.786 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Received: 5 bytes, 1 commands in the stack
2020-07-04 19:43:45.786 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Buffer: $-1
2020-07-04 19:43:45.786 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Stack contains: 1 commands
2020-07-04 19:43:45.786 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decode AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 19:43:45.786 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decoded AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], empty stack: true
2020-07-04 19:43:45.817 14548 --- [nio-8080-exec-2] DEBUG c.y.d.d.P.fetchAddress - ==> Preparing: SELECT id, uid, name, address, mobile FROM tb_deliver_address WHERE uid = ?
2020-07-04 19:43:45.834 14548 --- [nio-8080-exec-2] DEBUG c.y.d.d.P.fetchAddress - ==> Parameters: zhangsan(String)
2020-07-04 19:43:45.857 14548 --- [nio-8080-exec-2] DEBUG c.y.d.d.P.fetchAddress - <== Total: 4
2020-07-04 19:43:45.875 14548 --- [nio-8080-exec-2] DEBUG io.lettuce.core.RedisChannelHandler - dispatching command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 19:43:45.876 14548 --- [nio-8080-exec-2] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 19:43:45.876 14548 --- [nio-8080-exec-2] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() done
2020-07-04 19:43:45.876 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] write(ctx, AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise)
2020-07-04 19:43:45.877 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] writing command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 19:43:45.877 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] Sent: *5
$3
SET
$16
address:zhangsan
$538
? ArrayList?'com.yorozuyas.demo.model.DeliverAddresszhangsan鎴戠殑濮愬"-婀栧寳鐪佹姹夊競姹熸眽鍖哄彂灞曞ぇ閬?185鍙?(靖瘙-?'com.yorozuyas.demo.model.DeliverAddresszhangsan鎴戠殑濡瑰"6瀹夊窘鐪佸悎鑲ュ競缁忔祹鎶?鏈紑鍙戝尯鑺欒搲璺?678鍙?(扛瘙-?'com.yorozuyas.demo.model.DeliverAddresszhangsan鎴戠殑寮熷紵"9娴欐睙鐪佹澀宸炲競婊ㄦ睙鍖烘睙鍗楀ぇ閬撻緳婀栨花姹熷ぉ琛?(栏瘙-?'com.yorozuyas.demo.model.DeliverAddresszhangsan寮犱笁">骞夸笢鐪佷經灞卞競椤哄痉鍖哄ぇ鑹繋瀹捐矾纰ф鍥烽捇鐭虫咕(粮瘙-
$2
PX
$7
1800000
从控制台可以看出,在请求过来之后,SpringCache注解起作用了,首先是执行登录请求,然后查询address:zhangsan
,结果返回 Buffer: $-1
,说明缓存中没有这条信息,那么就执行数据库查询操作,最后把查询结果添加到缓存中。
2020-07-04 20:13:36.682 14548 --- [nio-8080-exec-5] INFO c.y.d.c.Protobuf2RedisController - Request-Method: GET, Request-Path: /fetch, uid: zhangsan
2020-07-04 20:13:36.705 14548 --- [nio-8080-exec-5] DEBUG io.lettuce.core.RedisChannelHandler - dispatching command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 20:13:36.705 14548 --- [nio-8080-exec-5] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 20:13:36.706 14548 --- [nio-8080-exec-5] DEBUG i.l.core.protocol.DefaultEndpoint - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, epid=0x1] write() done
2020-07-04 20:13:36.706 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] write(ctx, AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise)
2020-07-04 20:13:36.706 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] writing command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 20:13:36.707 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandEncoder - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379] Sent: *2
$3
GET
$16
address:zhangsan
2020-07-04 20:13:36.715 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Received: 512 bytes, 1 commands in the stack
2020-07-04 20:13:36.715 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Buffer: $538
? ArrayList?'com.yorozuyas.demo.model.DeliverAddresszhangsan鎴戠殑濮愬"-婀栧寳鐪佹姹夊競姹熸眽鍖哄彂灞曞ぇ閬?185鍙?(靖瘙-?'com.yorozuyas.demo.model.DeliverAddresszhangsan鎴戠殑濡瑰"6瀹夊窘鐪佸悎鑲ュ競缁忔祹鎶?鏈紑鍙戝尯鑺欒搲璺?678鍙?(扛瘙-?'com.yorozuyas.demo.model.DeliverAddresszhangsan鎴戠殑寮熷紵"9娴欐睙鐪佹澀宸炲競婊ㄦ睙鍖烘睙鍗楀ぇ閬撻緳婀栨花姹熷ぉ琛?(栏瘙-?'com.yorozuyas.demo.model.DeliverAddresszhangsan寮犱笁">骞夸笢鐪佷經灞卞競椤哄痉鍖哄ぇ鑹繋瀹
2020-07-04 20:13:36.715 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Stack contains: 1 commands
2020-07-04 20:13:36.715 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decode AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 20:13:36.715 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decoded AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], empty stack: false
2020-07-04 20:13:36.716 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Received: 34 bytes, 1 commands in the stack
2020-07-04 20:13:36.716 14548 --- [ioEventLoop-4-1] TRACE i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Buffer: 捐矾纰ф鍥烽捇鐭虫咕(粮瘙-
2020-07-04 20:13:36.716 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.CommandHandler - [channel=0xc4d24ef3, /192.168.3.16:50627 -> www.yorozuyas.com/121.36.211.56:6379, chid=0x1] Stack contains: 1 commands
2020-07-04 20:13:36.716 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decode AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2020-07-04 20:13:36.716 14548 --- [ioEventLoop-4-1] DEBUG i.l.core.protocol.RedisStateMachine - Decoded AsyncCommand [type=GET, output=ValueOutput [output=[B@2e463c2d, error='null'], commandType=io.lettuce.core.protocol.Command], empty stack: true
可以看到,执行了查询Redis,并得到结果Buffer: $538
,然后就返回数据,没有执行数据库操作(没有打印SQL信息判断得知)。
另外,redis
缓存的数据分别打印了两次,出现了Netty
的读写半包,有兴趣的同学可以学习一下Netty,可参考《Netty权威指南》或《Netty实战》。
org.springframework.data.redis.cache.RedisCacheConfiguration
,针对的是全局的,也就是默认的。redis
可以针对不同的命名空间提供不同的配置,具体需要自定义一个RedisCacheManager Bean
,并执行RedisCacheManagerBuilder
方法withInitialCacheConfigurations
添加Map
。