From 7bff2f3906b58780df644c9c0b5ab34db3570490 Mon Sep 17 00:00:00 2001 From: "Yangkai.Shen" <237497819@qq.com> Date: Thu, 15 Nov 2018 20:04:24 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20spring-boot-demo-cache-redis=20?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-boot-demo-cache-redis/README.md | 355 ++++++++++++++++++ spring-boot-demo-cache-redis/pom.xml | 33 ++ .../cache/redis/config/RedisConfig.java | 44 +++ .../com/xkcoding/cache/redis/entity/User.java | 35 ++ .../cache/redis/service/UserService.java | 41 ++ .../redis/service/impl/UserServiceImpl.java | 83 ++++ .../src/main/resources/application.properties | 0 .../src/main/resources/application.yml | 23 ++ .../com/xkcoding/cache/redis/RedisTest.java | 57 +++ .../cache/redis/service/UserServiceTest.java | 65 ++++ 10 files changed, 736 insertions(+) create mode 100644 spring-boot-demo-cache-redis/README.md create mode 100644 spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java create mode 100644 spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java create mode 100644 spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java create mode 100644 spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java delete mode 100644 spring-boot-demo-cache-redis/src/main/resources/application.properties create mode 100644 spring-boot-demo-cache-redis/src/main/resources/application.yml create mode 100644 spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java create mode 100644 spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java diff --git a/spring-boot-demo-cache-redis/README.md b/spring-boot-demo-cache-redis/README.md new file mode 100644 index 0000000..0dabb95 --- /dev/null +++ b/spring-boot-demo-cache-redis/README.md @@ -0,0 +1,355 @@ +# spring-boot-demo-cache-redis + +> 此 demo 主要演示了 Spring Boot 如何整合 redis,操作redis中的数据,并使用redis缓存数据。连接池使用 Lettuce。 + +## pom.xml + +```xml + + + 4.0.0 + + spring-boot-demo-cache-redis + 1.0.0-SNAPSHOT + jar + + spring-boot-demo-cache-redis + Demo project for Spring Boot + + + com.xkcoding + spring-boot-demo + 1.0.0-SNAPSHOT + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + org.springframework.boot + spring-boot-starter-json + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.google.guava + guava + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + + + + + spring-boot-demo-cache-redis + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + +## application.yml + +```yaml +spring: + redis: + host: localhost + # 连接超时时间(记得添加单位,Duration) + timeout: 10000ms + # Redis默认情况下有16个分片,这里配置具体使用的分片 + # database: 0 + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: -1ms + # 连接池中的最大空闲连接 默认 8 + max-idle: 8 + # 连接池中的最小空闲连接 默认 0 + min-idle: 0 + cache: + # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 + type: redis +logging: + level: + com.xkcoding: debug +``` + +## RedisConfig.java + +```java +/** + *

+ * redis配置 + *

+ * + * @package: com.xkcoding.cache.redis.config + * @description: redis配置 + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:41 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Configuration +@AutoConfigureAfter(RedisAutoConfiguration.class) +@EnableCaching +public class RedisConfig { + + /** + * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 + */ + @Bean + public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } +} +``` + +## UserServiceImpl.java + +```java +/** + *

+ * UserService + *

+ * + * @package: com.xkcoding.cache.redis.service.impl + * @description: UserService + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:45 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + /** + * 模拟数据库 + */ + private static final Map DATABASES = Maps.newConcurrentMap(); + + /** + * 初始化数据 + */ + static { + DATABASES.put(1L, new User(1L, "user1")); + DATABASES.put(2L, new User(2L, "user2")); + DATABASES.put(3L, new User(3L, "user3")); + } + + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + @CachePut(value = "user", key = "#user.id") + @Override + public User saveOrUpdate(User user) { + DATABASES.put(user.getId(), user); + log.info("保存用户【user】= {}", user); + return user; + } + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + @Cacheable(value = "user", key = "#id") + @Override + public User get(Long id) { + // 我们假设从数据库读取 + log.info("查询用户【id】= {}", id); + return DATABASES.get(id); + } + + /** + * 删除 + * + * @param id key值 + */ + @CacheEvict(value = "user", key = "#id") + @Override + public void delete(Long id) { + DATABASES.remove(id); + log.info("删除用户【id】= {}", id); + } +} +``` + +## RedisTest.java + +> 主要测试使用 `RedisTemplate` 操作 `Redis` 中的数据: +> +> - opsForValue:对应 String(字符串) +> - opsForZSet:对应 ZSet(有序集合) +> - opsForHash:对应 Hash(哈希) +> - opsForList:对应 List(列表) +> - opsForSet:对应 Set(集合) +> - opsForGeo:** 对应 GEO(地理位置) + +```java +/** + *

+ * Redis测试 + *

+ * + * @package: com.xkcoding.cache.redis + * @description: Redis测试 + * @author: yangkai.shen + * @date: Created in 2018/11/15 17:17 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +public class RedisTest extends SpringBootDemoCacheRedisApplicationTests { + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Autowired + private RedisTemplate redisCacheTemplate; + + /** + * 测试 Redis 操作 + */ + @Test + public void get() { + // 测试线程安全,程序结束查看redis中count的值是否为1000 + ExecutorService executorService = Executors.newFixedThreadPool(1000); + IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1))); + + stringRedisTemplate.opsForValue().set("k1", "v1"); + String k1 = stringRedisTemplate.opsForValue().get("k1"); + log.debug("【k1】= {}", k1); + + // 以下演示整合,具体Redis命令可以参考官方文档 + String key = "xkcoding:user:1"; + redisCacheTemplate.opsForValue().set(key, new User(1L, "user1")); + // 对应 String(字符串) + User user = (User) redisCacheTemplate.opsForValue().get(key); + log.debug("【user】= {}", user); + } +} + +``` + +## UserServiceTest.java + +> 主要测试使用Redis缓存是否起效 + +```java +/** + *

+ * Redis - 缓存测试 + *

+ * + * @package: com.xkcoding.cache.redis.service + * @description: Redis - 缓存测试 + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:53 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests { + @Autowired + private UserService userService; + + /** + * 获取两次,查看日志验证缓存 + */ + @Test + public void getTwice() { + // 模拟查询id为1的用户 + User user1 = userService.get(1L); + log.debug("【user1】= {}", user1); + + // 再次查询 + User user2 = userService.get(1L); + log.debug("【user2】= {}", user2); + // 查看日志,只打印一次日志,证明缓存生效 + } + + /** + * 先存,再查询,查看日志验证缓存 + */ + @Test + public void getAfterSave() { + userService.saveOrUpdate(new User(4L, "user4")); + + User user = userService.get(4L); + log.debug("【user】= {}", user); + // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 + } + + /** + * 测试删除,查看redis是否存在缓存数据 + */ + @Test + public void deleteUser() { + // 查询一次,使redis中存在缓存数据 + userService.get(1L); + // 删除,查看redis是否存在缓存数据 + userService.delete(1L); + } + +} +``` + +## 参考 + +- spring-data-redis 官方文档:https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/ +- redis 文档:https://redis.io/documentation +- redis 中文文档:http://www.redis.cn/commands.html \ No newline at end of file diff --git a/spring-boot-demo-cache-redis/pom.xml b/spring-boot-demo-cache-redis/pom.xml index 6b6e7c4..01fcc38 100644 --- a/spring-boot-demo-cache-redis/pom.xml +++ b/spring-boot-demo-cache-redis/pom.xml @@ -28,11 +28,44 @@ spring-boot-starter + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + org.springframework.boot + spring-boot-starter-json + + org.springframework.boot spring-boot-starter-test test + + + com.google.guava + guava + + + + cn.hutool + hutool-all + + + + org.projectlombok + lombok + true + diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java new file mode 100644 index 0000000..cfd9404 --- /dev/null +++ b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/config/RedisConfig.java @@ -0,0 +1,44 @@ +package com.xkcoding.cache.redis.config; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.io.Serializable; + +/** + *

+ * redis配置 + *

+ * + * @package: com.xkcoding.cache.redis.config + * @description: redis配置 + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:41 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Configuration +@AutoConfigureAfter(RedisAutoConfiguration.class) +@EnableCaching +public class RedisConfig { + + /** + * 默认情况下的模板只能支持RedisTemplate,也就是只能存入字符串,因此支持序列化 + */ + @Bean + public RedisTemplate redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setConnectionFactory(redisConnectionFactory); + return template; + } +} diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java new file mode 100644 index 0000000..3474dcb --- /dev/null +++ b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/entity/User.java @@ -0,0 +1,35 @@ +package com.xkcoding.cache.redis.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

+ * 用户实体 + *

+ * + * @package: com.xkcoding.cache.redis.entity + * @description: 用户实体 + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:39 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class User implements Serializable { + private static final long serialVersionUID = 2892248514883451461L; + /** + * 主键id + */ + private Long id; + /** + * 姓名 + */ + private String name; +} diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java new file mode 100644 index 0000000..0da3c4a --- /dev/null +++ b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/UserService.java @@ -0,0 +1,41 @@ +package com.xkcoding.cache.redis.service; + +import com.xkcoding.cache.redis.entity.User; + +/** + *

+ * UserService + *

+ * + * @package: com.xkcoding.cache.redis.service + * @description: UserService + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:45 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +public interface UserService { + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + User saveOrUpdate(User user); + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + User get(Long id); + + /** + * 删除 + * + * @param id key值 + */ + void delete(Long id); +} diff --git a/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..4942d0d --- /dev/null +++ b/spring-boot-demo-cache-redis/src/main/java/com/xkcoding/cache/redis/service/impl/UserServiceImpl.java @@ -0,0 +1,83 @@ +package com.xkcoding.cache.redis.service.impl; + +import com.google.common.collect.Maps; +import com.xkcoding.cache.redis.entity.User; +import com.xkcoding.cache.redis.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + *

+ * UserService + *

+ * + * @package: com.xkcoding.cache.redis.service.impl + * @description: UserService + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:45 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + /** + * 模拟数据库 + */ + private static final Map DATABASES = Maps.newConcurrentMap(); + + /** + * 初始化数据 + */ + static { + DATABASES.put(1L, new User(1L, "user1")); + DATABASES.put(2L, new User(2L, "user2")); + DATABASES.put(3L, new User(3L, "user3")); + } + + /** + * 保存或修改用户 + * + * @param user 用户对象 + * @return 操作结果 + */ + @CachePut(value = "user", key = "#user.id") + @Override + public User saveOrUpdate(User user) { + DATABASES.put(user.getId(), user); + log.info("保存用户【user】= {}", user); + return user; + } + + /** + * 获取用户 + * + * @param id key值 + * @return 返回结果 + */ + @Cacheable(value = "user", key = "#id") + @Override + public User get(Long id) { + // 我们假设从数据库读取 + log.info("查询用户【id】= {}", id); + return DATABASES.get(id); + } + + /** + * 删除 + * + * @param id key值 + */ + @CacheEvict(value = "user", key = "#id") + @Override + public void delete(Long id) { + DATABASES.remove(id); + log.info("删除用户【id】= {}", id); + } +} diff --git a/spring-boot-demo-cache-redis/src/main/resources/application.properties b/spring-boot-demo-cache-redis/src/main/resources/application.properties deleted file mode 100644 index e69de29..0000000 diff --git a/spring-boot-demo-cache-redis/src/main/resources/application.yml b/spring-boot-demo-cache-redis/src/main/resources/application.yml new file mode 100644 index 0000000..b078e9c --- /dev/null +++ b/spring-boot-demo-cache-redis/src/main/resources/application.yml @@ -0,0 +1,23 @@ +spring: + redis: + host: localhost + # 连接超时时间(记得添加单位,Duration) + timeout: 10000ms + # Redis默认情况下有16个分片,这里配置具体使用的分片 + # database: 0 + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: -1ms + # 连接池中的最大空闲连接 默认 8 + max-idle: 8 + # 连接池中的最小空闲连接 默认 0 + min-idle: 0 + cache: + # 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 + type: redis +logging: + level: + com.xkcoding: debug diff --git a/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java b/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java new file mode 100644 index 0000000..55e766f --- /dev/null +++ b/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/RedisTest.java @@ -0,0 +1,57 @@ +package com.xkcoding.cache.redis; + +import com.xkcoding.cache.redis.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.io.Serializable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.IntStream; + +/** + *

+ * Redis测试 + *

+ * + * @package: com.xkcoding.cache.redis + * @description: Redis测试 + * @author: yangkai.shen + * @date: Created in 2018/11/15 17:17 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +public class RedisTest extends SpringBootDemoCacheRedisApplicationTests { + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Autowired + private RedisTemplate redisCacheTemplate; + + /** + * 测试 Redis 操作 + */ + @Test + public void get() { + // 测试线程安全,程序结束查看redis中count的值是否为1000 + ExecutorService executorService = Executors.newFixedThreadPool(1000); + IntStream.range(0, 1000).forEach(i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("count", 1))); + + stringRedisTemplate.opsForValue().set("k1", "v1"); + String k1 = stringRedisTemplate.opsForValue().get("k1"); + log.debug("【k1】= {}", k1); + + // 以下演示整合,具体Redis命令可以参考官方文档 + String key = "xkcoding:user:1"; + redisCacheTemplate.opsForValue().set(key, new User(1L, "user1")); + // 对应 String(字符串) + User user = (User) redisCacheTemplate.opsForValue().get(key); + log.debug("【user】= {}", user); + } +} diff --git a/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java b/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java new file mode 100644 index 0000000..9d15219 --- /dev/null +++ b/spring-boot-demo-cache-redis/src/test/java/com/xkcoding/cache/redis/service/UserServiceTest.java @@ -0,0 +1,65 @@ +package com.xkcoding.cache.redis.service; + +import com.xkcoding.cache.redis.SpringBootDemoCacheRedisApplicationTests; +import com.xkcoding.cache.redis.entity.User; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + *

+ * Redis - 缓存测试 + *

+ * + * @package: com.xkcoding.cache.redis.service + * @description: Redis - 缓存测试 + * @author: yangkai.shen + * @date: Created in 2018/11/15 16:53 + * @copyright: Copyright (c) 2018 + * @version: V1.0 + * @modified: yangkai.shen + */ +@Slf4j +public class UserServiceTest extends SpringBootDemoCacheRedisApplicationTests { + @Autowired + private UserService userService; + + /** + * 获取两次,查看日志验证缓存 + */ + @Test + public void getTwice() { + // 模拟查询id为1的用户 + User user1 = userService.get(1L); + log.debug("【user1】= {}", user1); + + // 再次查询 + User user2 = userService.get(1L); + log.debug("【user2】= {}", user2); + // 查看日志,只打印一次日志,证明缓存生效 + } + + /** + * 先存,再查询,查看日志验证缓存 + */ + @Test + public void getAfterSave() { + userService.saveOrUpdate(new User(4L, "user4")); + + User user = userService.get(4L); + log.debug("【user】= {}", user); + // 查看日志,只打印保存用户的日志,查询是未触发查询日志,因此缓存生效 + } + + /** + * 测试删除,查看redis是否存在缓存数据 + */ + @Test + public void deleteUser() { + // 查询一次,使redis中存在缓存数据 + userService.get(1L); + // 删除,查看redis是否存在缓存数据 + userService.delete(1L); + } + +} \ No newline at end of file