利用Guava Cache作为本地缓存
前言
虽然我们一般都用Redis缓存但是,在一些小的项目中可以不使用Redis,而是利用Guava Cache来做本地缓存,这么做本地缓存的速度会比Redis要快一点。
虽然Spring Boot默认使用的是 SimpleCacheConfiguration。但是这个Guava Cache在其基础上,有了不同程度的优化。所以在做一些小项目的时候呢,可以用这个来做缓存。
内部实现
Guava Cache使用 ConcurrentMapCacheManager来实现的缓存
Guava Cache 与 ConcurrentMap二者很相似,但也不完全一样。最基本的区别是 ConcurrentMap 会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache 为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管 LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。
区别
Redis自不用说,是一个在多个服务器之间能够实现集群和哨兵的缓存,相比之下,Guava Cache就稍稍弱了些。
如果数据量不大,且多个服务之间没有相互同步数据的需要,则使用Guava Cache是非常理想的,它使用简单且性能很好。如果需要缓存的数据量很大或者多个服务之间需要共享缓存数据,则redis是理想的选择,虽然读取性能对比Guava Cache满了很多,但是绝对值并不大,大多数情况下满足我们的需要。
还有一点比较大区别就是他们的拷贝程度不同。
Redis 缓存是深拷贝
从 Redis 中获取缓存时,系统中的数据对象是 Redis 缓存的副本。对该对象的任何操作都不会影响 Redis 中的缓存,后续再次获取还是修改之前的数据。除非执行Redis的更新操作。
Guava本地缓存直接获取则是浅拷贝
以获取一个MAP为例:如果直接从缓存中取,则是浅拷贝。
对缓存数据的任何操作都会同时修改缓存中的数据,下次从缓存中获取则是修改之后的数据。一般不会修改从缓存中获取到的数据,但如果要修改,则需注意Redis和Guava的不同。
所以如果只是做一个比较小的项目。就不用特地去连接redis。还是使用我们本地的缓存更为快速。
实现
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.0.1-jre</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.24.RELEASE</version> </dependency>
|
Config
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Configuration @EnableCaching public class GuavaCacheConfig {
@Bean public CacheManager cacheManager() { GuavaCacheManager cacheManager = new GuavaCacheManager(); cacheManager.setCacheBuilder( CacheBuilder.newBuilder(). expireAfterWrite(10, TimeUnit.SECONDS). maximumSize(1000)); return cacheManager; } }
|
这里是初始化一个对象并设置它的存活时间等等。我们把这个方法注入到spring的上下文当中。而每当我们需要去数据库读取数据时,spring就会先返回这个缓存的实体,将数据先存入到本地缓存当中。
这里我们设置它的存活时间是十秒。
用户类
首先需要一个实体类和与其对应的数据表。这也就只列出实体类,大家可以自己对应一下。
1 2 3 4 5 6
| @Data public class User { private Long userId; private String userName; private Integer userAge; }
|
当然。还有配置文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| mybatis: mapper-locations: classpath:/mapper/*Mapper.xml type-aliases-package: com.example.guavacache.pojo
spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/springtest?useSSL=false&serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8 driver-class-name: com.mysql.cj.jdbc.Driver
server: port: 8888
|
服务层
然后就是服务层,要实现的方法。
1 2 3 4 5
| public interface UserMapper { List<User> getUsers(); int addUser(User user); List<User> getUsersByName(String userName ); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Service public class UserService { @Autowired private UserMapper userMapper;
public List<User> getUsers() { return userMapper.getUsers(); }
public int addUser( User user ) { return userMapper.addUser(user); }
@Cacheable(value = "user", key = "#userName") public List<User> getUsersByName( String userName ) { List<User> users = userMapper.getUsersByName( userName ); System.out.println( "从数据库读取,而非读取缓存!" ); return users; } }
|
控制层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @RestController public class UserController { @Autowired private UserService userService;
@Autowired CacheManager cacheManager;
@RequestMapping( value = "/getusersbyname", method = RequestMethod.POST) public List<User> geUsersByName(@RequestBody User user ) { System.out.println( "-------------------------------------------" ); System.out.println("call /getusersbyname"); System.out.println(cacheManager.toString()); List<User> users = userService.getUsersByName( user.getUserName() ); return users; } }
|
控制层使用这个方法去调用,以便用于测试。
最后还有在主类上加上一个注解:
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableCaching @MapperScan("com.example.guavacache.mapper") public class GuavacacheApplication {
public static void main(String[] args) { SpringApplication.run(GuavacacheApplication.class, args); }
|
这样子就完成了。
测试
我们可以打开这个PostMan,使用 localhost:8888/getusersbyname 发起POST请求,就可以测试出结果了。
但由于比较简单,这里就不放图了。