1、redis下载和本地启动
下载地址:https://github.com/tporadowski/redis/releases
下载 Redis-x64-xxx.zip压缩包,将其解压到名为 redis的文件夹。
本地启动:进入redis目录,在cmd窗口运行命令:
redis-server.exe redis.windows.conf
,redis便可成功启动。下图是redis启动成功的界面:
2、pom文件中导入redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
3、yml文件中配置redis的链接
spring: # 配置redis连接 redis: # Redis服务器地址 host: 127.0.0.1 # Redis服务器连接端口 port: 6379 jedis: pool: # 连接池最大连接数(使用负值表示没有限制) max-active: 10 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: 1000 # 连接超时时间(毫秒) connect-timeout: 1000
4、配置RedisTemplate<String, Object>
SpringBoot框架默认在org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration类下配置了两个RedisTemplate:
RedisTemplate<Object, Object>
key和value都是Object类型,并且都需要实现Serializable接口。
StringRedisTemplate,即RedisTemplate<String, String>
key和value都是String类型,当需要存储实体类时,需要先将其转为JSON格式的字符串,再存入Redis。
RedisAutoConfiguration类源码如下:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean(name = {"redisTemplate"}) @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
SpringBoot配置好的两个RedisTemplate都不太好用,因为将实体类手动转换为JSON格式字符串的过程比较麻烦,所以在项目中可以自己配置一个RedisTemplate<String, Object>的Bean。代码如下:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); // 配置连接工厂 template.setConnectionFactory(factory); // key使用StringRedisSerializer进行序列化 template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); // value使用GenericJackson2JsonRedisSerializer进行序列化 template.setValueSerializer(genericJackson2JsonRedisSerializer); template.setHashValueSerializer(genericJackson2JsonRedisSerializer); return template; } }
那么RedisTemplate配置连接的工厂RedisConnectionFactory是从哪儿来的呢?我们不妨在redisTemplate方法中先将RedisConnectionFactory对象打印出来,看看它到底是什么东西。
System.out.println("factory: " + factory);
由于配置类是在SpringBoot项目启动时就加载到Spring容器中,所以在项目启动时,控制台展示了上面语句对factory的打印。
factory: org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory@1f3165e7
可以看出,这个RedisConnectionFactory工厂对象实际是LettuceConnectionFactory的一个实例,LettuceConnectionFactory实现了RedisConnectionFactory接口。是那么LettuceConnectionFactory的实例又是在哪加载的呢?
继续探索发现,在org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration类中创建了LettuceClientConfiguration实例并放到了Spring容器中。部分代码如下:
@Bean @ConditionalOnMissingBean({RedisConnectionFactory.class}) LettuceConnectionFactory redisConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ClientResources clientResources) { LettuceClientConfiguration clientConfig = this.getLettuceClientConfiguration(builderCustomizers, clientResources, this.getProperties().getLettuce().getPool()); return this.createLettuceConnectionFactory(clientConfig); }
5、redis测试应用
应用场景:如果redis中存在key值对应的缓存数据,则取redis中的数据;如果redis中不存在key值对应的缓存数据,则去从数据库中取值。
service层代码如下:
/** * 这里仅展示了Service层代码 * */ @Service @Transactional public class TestServiceImpl implements ITestService { @Resource private TestMapper testMapper; @Resource private RedisTemplate<String, Object> template; @Override public UserVO test(int userId) { ValueOperations<String, Object> redisString = template.opsForValue(); String redisKey = "user"; UserVO userVO = null; Instant start = Instant.now(); if (redisString.get(redisKey) == null) { System.out.println("====从数据库中获取数据===="); userVO = testMapper.test(userId); redisString.set(redisKey, userVO, 7L, TimeUnit.DAYS); } else { System.out.println("====从redis中获取数据===="); userVO = (UserVO) redisString.get(redisKey); } System.out.println(userVO); Instant end = Instant.now(); System.out.println("耗时:" + Duration.between(start, end).toMillis() + "毫秒"); return userVO; } }
redis设置key值时有两点注意事项:
- 设置key时必须设置过期时间。
- 设置key时必须加上指定前缀,方便批量操作。
启动SpringBoot项目后,用Postman去调本地接口,程序运行控制台打印结果如下:
====从数据库中获取数据==== Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@391c1891] JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@45a49218] will be managed by Spring ==> Preparing: select id as userId, name as userName from test_users where id = ?; ==> Parameters: 1011(Integer) <== Columns: userId, userName <== Row: 1011, 橘子右 <== Total: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@391c1891] UserVO(userId=1011, userName=橘子右) 耗时:1657毫秒 Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@391c1891] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@391c1891] Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@391c1891]
Postman第一次调本地接口时已经将获取到的数据放到了redis缓存中。由于SpringBoot项目懒加载机制的特性,启动后第一次操作往往会花费较多时间,为了保证公平性,重启SpringBoot项目后,再次用用Postman去调本地接口,程序运行控制台打印结果如下:
====从redis中获取数据==== UserVO(userId=1011, userName=橘子右) 耗时:1589毫秒
经过多次试验,由结果可知,从redis中取数据要比从数据库中取数据更快。由于查询的数据量较小,所以使用redis只比查数据库快那么一丢丢,但是如果操作的数据量较大的话,那么它们之间的性能差距就会更加明显。
为什么使用redis缓存的操作会更快?原因是Oracle、Mysql这些关系型数据库的数据存放在磁盘里,在获取数据时需要执行I/O操作;而redis是一个内存数据库,它的数据都存放在内存中。因为内存的读写速度相较于磁盘的读写要快很多,因此,从redis中获取数据更快,所以redis常被用来做页面、数据的缓存。
在SpringBoot中配置了Redis后,启动项目会有如下日志信息:
INFO 1696 --- [main] .s.d.r.c.RepositoryConfigurationDelegate: Multiple Spring Data modules found, entering strict repository configuration mode!
虽然这条日志信息是info级别的,但是该日志信息后面由感叹号(一般info级别是没有感叹号的),所以值得深究一下。
由控制台报错信息可知,该报错信息是在org.springframework.data.repository.config包下的RepositoryConfigurationDelegate中的multipleStoresDetected()方法里打印到日志的。multipleStoresDetected()方法如下:
private boolean multipleStoresDetected() { boolean multipleModulesFound = SpringFactoriesLoader.loadFactoryNames(RepositoryFactorySupport.class, this.resourceLoader.getClassLoader()).size() > 1; if (multipleModulesFound) { logger.info("Multiple Spring Data modules found, entering strict repository configuration mode!"); } return multipleModulesFound; }
从代码可知,如果RepositoryFactorySupport这个抽象类及其字类如果超过一个,就会显示该日志信息。
全局搜RepositoryFactorySupport,可以发现有两处RepositoryFactorySupport的应用:
第一处是spring-data-keyvalue.jar包的META-INF目录下的spring.factories文件。代码如下:
org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory
第二处是spring-data-redis.jar包的META-INF目录下的spring.factories文件。代码如下:
org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.redis.repository.support.RedisRepositoryFactory
由此可知,正是由于存在两个RepositoryFactorySupport,所以会有“Multiple Spring Data modules found”的日志信息。
解决方案:使用如下两种方案中的任何一种,重启SpringBoot项目,“Multiple Spring Data modules found”的日志信息将会消失。
方案一:yml文件中关闭redis的repository功能。
spring: data: redis: repositories: enabled: false
方案二:在SpringBoot项目的启动类中排除repository自动装载。
@SpringBootApplication(exclude = RedisRepositoriesAutoConfiguration.class) @MapperScan(basePackages = "com.example.demo.dao") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }