Spring boot下的应用访问Redis数据库,采用属性配置文件读取数据,哨兵方式访问数据库。
pom文件
在pom文件中引入依赖包:
<dependency> <groupId>org.springframework.boot <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置文件
在application.properties配置文件中添加:
# ------------------------------------------------ # Redis配置 # 是否启用Redis服务 redis.redisService.enabled=false # redis 连接 # 客户端超时时间单位是毫秒 默认是2000,未使用 redis.timeout=2000 # 最大空闲数 redis.maxIdle=300 # 连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal #redis.maxActive=600 # 控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性 redis.maxTotal=1000 # 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。 redis.maxWaitMillis=1000 # 连接的最小空闲时间 默认1800000毫秒(30分钟) redis.minEvictableIdleTimeMillis=300000 # 每次释放连接的最大数目,默认3 redis.numTestsPerEvictionRun=1024 # 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 redis.timeBetweenEvictionRunsMillis=30000 # 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 redis.testOnBorrow=true # 在空闲时检查有效性, 默认false redis.testWhileIdle=true # 哨兵 redis.sentinel.master=mymaster redis.sentinel.nodes=127.0.0.1:9379 redis.database=0 redis.password=
这里要说明一下,redis.redisService.enabled 是标识是否需要启用访问redis功能,特别是在使用自身电脑在移动办公开发时,有时并不需要连接Redis服务(例如我)。具体实现的原理是通过 @ConditionalOnExpression(“${redis.redisService.enabled:true}”) 实现对bean的注入控制。
redis配置注入
使用注解 @Configuration 声明java文件作为配置文件,注入相关的java类,完整文件见 redis\RedisCacheConfig.java。
为方便理解,以思考顺序描述相关内容。
注入redisService类
spring-data-redis针对jedis提供了一个高度封装的 RedisTemplate 类,针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口。
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
由于业务逻辑使用时,需要@Autowired注入redisService类,所以此处进行注入类的声明。
/** * 注入封装RedisTemplate */ @Bean(name = "redisService") @ConditionalOnExpression("${redis.redisService.enabled:true}") public RedisService redisService(RedisTemplate<String, Object> redisTemplate) { RedisService redisService = new RedisService(); redisService.setRedisTemplate(redisTemplate); return redisService; }
在进行redisService初始化时,需要参数对象RedisTemplate,有了下文的注入RedisTemplate类。
注入RedisTemplate类
此处使用JedisPoolConfig进行RedisTemplate的配置,同时设置序列化方式。
/** * RedisTemplate * * @return redisTemplate */ @Bean @ConditionalOnExpression("${redis.redisService.enabled:true}") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // 设置数据存入 redis 的序列化方式 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 事务开关 redisTemplate.setEnableTransactionSupport(false); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; }
此处的初始化需要redisConnectionFactory类。所以有redisConnectionFactory类的注入。
注入redisConnectionFactory类
通过连接工厂,以哨兵的方式连接Redis数据库服务。
/** * jedis连接工厂 * @param jedisPoolConfig * @return */ @Bean @ConditionalOnExpression("${redis.redisService.enabled:true}") public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) { //哨兵 RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(); String[] host = redisNodes.split(","); for(String redisHost : host){ String[] item = redisHost.split(":"); String ip = item[0]; String port = item[1]; redisSentinelConfiguration.addSentinel(new RedisNode(ip, Integer.parseInt(port))); } redisSentinelConfiguration.setDatabase(database); redisSentinelConfiguration.setMaster(master); //获得默认的连接池构造器 JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder(); //指定jedisPoolConifig来修改默认的连接池构造器 jpcb.poolConfig(jedisPoolConfig); //通过构造器来构造jedis客户端配置 JedisClientConfiguration jedisClientConfiguration = jpcb.build(); //jedis连接工厂 return new JedisConnectionFactory(redisSentinelConfiguration, jedisClientConfiguration); } 注入jedisPoolConfig类 edisPoolConfig类配置所需的参数由@Value从配置文件中读取。 /** * 连接池配置信息 * @return */ @Bean @ConditionalOnExpression("${redis.redisService.enabled:true}") public JedisPoolConfig jedisPoolConfig() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // 最大空闲数 jedisPoolConfig.setMaxIdle(maxIdle); // 连接池的最大数据库连接数 jedisPoolConfig.setMaxTotal(maxTotal); // 最大建立连接等待时间 jedisPoolConfig.setMaxWaitMillis(maxWaitMillis); // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟) jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); // 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun); // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 jedisPoolConfig.setTestOnBorrow(testOnBorrow); // 在空闲时检查有效性, 默认false jedisPoolConfig.setTestWhileIdle(testWhileIdle); return jedisPoolConfig; }
redis操作服务
@Component @Slf4j public class RedisService { private RedisTemplate<String, Object> redisTemplate; public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; }
//=============================common============================ /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key,long time){ try { if(time> 0){ redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { log.error("指定缓存失效时间异常,信息为:{}", e); return false; } }
redis服务使用
数据存储
注入redis服务
@Autowired private RedisService redisService;
存储数据
/** * 验证码存放在Redis中的itemkey */ public static final String ITEMKEY = "verifyCode"; /** * 验证码存放在Redis中的有效期,以秒为单位 */ public static final Integer EXPIRETIME = 180;
//验证码存放Redis,有效期3分钟 redisService.hset(Constant.ITEMKEY, mobile, verifyCode, Constant.EXPIRETIME);
//是否验证验证码 String realSmsCode = (String) redisService.hget(Constant.ITEM, mobile);
缓存mybatis数据
分布式项目中最常见的缓存机制就是通过redis缓存mybatis的查询数据,一般称为二级缓存,可以使用@CacheConfig,@Cacheable,@CachePut,@CacheEvict。
public interface UserMapper { @Cacheable(cacheNames = "User:Id") public User findById(@Param("id") Integer id); }
CacheManagerConfig
缓存对象集合中,缓存是以key-value形式保存的。当不指定缓存的key时,SpringBoot会使用SimpleKeyGenerator生成key。它是使用方法的参数组合生成的一个key。key采用 参数列表。
public class SimpleKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { return generateKey(params); } /** * Generate a key based on the specified parameters. */ public static Object generateKey(Object... params) { if (params.length == 0) { return SimpleKey.EMPTY; } if (params.length == 1) { Object param = params[0]; if (param != null && !param.getClass().isArray()) { return param; } } return new SimpleKey(params); } }
此时有一个问题:如果2个方法,参数是一样的,但执行逻辑不同,那么将会导致执行第二个方法时命中第一个方法的缓存。
解决办法是在@Cacheable注解参数中指定key,或者自己实现一个KeyGenerator,在注解中指定KeyGenerator。但是如果这样的情况很多,每一个都要指定key、KeyGenerator很麻烦。
Spring同样提供了方案:继承CachingConfigurerSupport并重写keyGenerator()。key采用 包名+方法名+参数列表。
完整代码文件见CacheManagerConfig.java。
@Bean @Override public KeyGenerator keyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... objects) { StringBuilder sb = new StringBuilder(); sb.append(o.getClass().getName()); sb.append(method.getName()); for (Object obj:objects){ sb.append(obj.toString()); } log.info("keyGenerator=" + sb.toString()); return sb.toString(); } }; }
redisCacheManager
同样使用redisConnectionFactory配置缓存的连接。
@Bean(name = "redisCacheManager") @ConditionalOnExpression("${redis.redisService.enabled:true}") public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheManager redisCacheManager = RedisCacheManager.create(redisConnectionFactory); log.info("redis初始化-----------------------------------"); return redisCacheManager; }