Spring boot 缓存
1. spring cache
spring cache 是spring 3.1 引入的新技术,
核心思想:调用一个缓存方法时会把该方法参数和返回结果,作为一个键值存入缓存中,等到下次使用同样的参数调用该方法时,不在执行该方法,直接从缓存中获取结果进行返回,从而实现缓存功能。
Spring 中提供了3个注解来实现缓存。
- @Cacheable
- @CachePut
- @CacheEvict
下面通过一个实例,来了解spring cache.这里使用的spring-data-jpa 。也可以使用spring-mybatis stater。
springboot 2.2.6
mybatis 8.0.15
maven 3.6.1
Jdk 11
配置文件
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 这里使用的是MySql 8.0.15 数据库,需要加时区,保证自己的数据库db_jdbc 存在。
spring.datasource.url=jdbc:mysql://localhost:3306/db_jpa?\
useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=666666
#JPA 配置
# validate加载Hibernate 时验证创建的数据库表
# create 每次加载hibernate,重新创建数据库表结构,这就是导致数据库丢失的原因
# drop-create 加载hibernate 时创建,退出时删除
# update 加载hibernate 时自动更新数据库
# none 启动时不做任何操作
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
# 控制台打印Sql
spring.jpa.show-sql=true
server.port=8080
- 引用依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</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>
主类上配置@EnableCaching // 开启缓存
@SpringBootApplication
//开启缓存
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
}
repository接口
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
控制类UserController
@Controller
@ResponseBody
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("findAll")
public List<User> findAll(){
List<User> userList = userRepository.findAll();
return userList;
}
//http://localhost:8080/saveUser?id=1&username=deyou&password=1234
//缓存测试CachePut
@GetMapping("saveUser")
//将返回值放入缓存
@CachePut(value = "user",key = "#id")
public User saveUser(Integer id, String username,String password){
User user = new User(id, username, password);
userRepository.save(user);
System.out.println("测试Save"+user);
return user;
}
//http://localhost:8080/queryUser?id=1
//查询用户
@GetMapping("/queryUser")
//使用这个注解,方法执行会先在缓存里找,如果有直接返回,若果没有返回值放入缓存,再返回。
@Cacheable(value = "user",key = "#id")
public Optional<User> queryUser(Integer id){
Optional<User> user = userRepository.findById(id);
return user;
}
//
@GetMapping("/deleteUserById")
//删除对应Id 的缓存,同时也把数据删除了。
@CacheEvict(value = "user" ,key ="#id")
public String deleteUserById(Integer id){
userRepository.deleteById(id);
return "delete success";
}
//http://localhost:8080/deleteCacheId?id=1
@GetMapping("/deleteCacheId")
//删除对应Id 的缓存,同时也把数据删除了。
@CacheEvict(value = "user" ,key ="#id")
public String deleteCacheId(Integer id){
return "delete cacheId " + id ;
}
@GetMapping("deleteCache")
//删除所有缓存
@CacheEvict(value = "user",allEntries = true)
public String deleteCache(){
return "clean all cache";
}
}
启动项目: 在浏览器上输出对应方法上的链接,观察缓存效果。
2. 使用 redis
使用redis 做缓存数据库。默认读者已经完成redis 的安装。
新建一个springboot 项目。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</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>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
application.properties 默认配置如下,如果不同自行更改
# 默认
spring.redis.host=localhost
# 默认
spring.redis.port=6379
选用User实体类和上面一样。这里User类实现了序列化接口。方便redis 对象实例化
@Data
@AllArgsConstructor
@NoArgsConstructor
@EntityScan
public class User implements Serializable {
private int id;
private String username;
private String password;
}
新建一个RedisService.java
@Service
public class RedisService {
@Resource
private RedisTemplate<String,Object> redisTemplate;
public void set(String key, Object value){
//将key值序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
//将值(Object)序列化
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
ValueOperations<String,Object> vo=redisTemplate.opsForValue();
vo.set(key, value);
}
public Object get(String key){
ValueOperations<String,Object> vo=redisTemplate.opsForValue();
return vo.get(key);
}
}
控制类UserController
@RestController
public class UserController {
@Autowired
private RedisService redisService;
//http://localhost:8080/saveUser?id=1&username=deyou&password=1234
@GetMapping("saveUser")
public String saveUser(Integer id,String username,String password){
User user = new User(id,username,password);
redisService.set(id.toString(),user);
return "success";
}
//http://localhost:8080/getUserById?id=1
@GetMapping("getUserById")
public Object getUserById(Integer id){
//id.toString 转为字符串
Object o = redisService.get(id.toString());
return o;
}
}
测试:必须在启动redis 服务器的情况下启动项目。
执行在浏览器中http://localhost:8080/saveUser?id=1&username=deyou&password=1234,将一个实例化对象存入Redis 数据库。
执行:http://localhost:8080/getUserById?id=1 将在浏览器中获得存入redis 中的对应的实例化对象数据。
同样开启redis 服务器可以进行如下测试
127.0.0.1:6379> keys *
- “1”
127.0.0.1:6379> get 1
“{“id”:1,“username”:“deyou”,“password”:“1234”}”
127.0.0.1:6379>
下面开始使用redis 做缓存数据库。
新建一个UserRepository 接口。
public interface UserRepository extends JpaRepository<User,Integer> {
}
RedisService 增加如下方法
//增加一个set方法
//设置key value 过期时间和过期单位,过期后还要从Mysql数据库中查询
public void set(String key, Object value, Long time, TimeUnit t ){
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
ValueOperations<String, Object> ov = redisTemplate.opsForValue();
ov.set(key,value,time,t);
}
UserContoller 增加如下代码
@Autowired
private UserRepository userRepository;
//对mysql 数据库插入一条数据
//http://localhost:8080/saveuser2?id=1&username=deyou&password=1234
@GetMapping("/saveuser2")
public User saveuser2(Integer id,String username,String password){
User user = new User(id,username,password);
User save = userRepository.save(user);
System.out.println(save);
return user;
}
//查询数据,本方法完成了redis 缓存功能
//http://localhost:8080/getUser?id=1
@GetMapping("getUser")
public Object getUser(Integer id){
Object o = redisService.get(id.toString());
//测试代码
System.out.println("redis中存在=========="+o);
if(o == null){
o = userRepository.findById(id).get();
if(o != null){
//可以自行修改时间测试,避免等待时间过长
redisService.set(id.toString(),o,100L, TimeUnit.SECONDS);
System.out.println("redis 中存入对象json串=========="+o);
}
}
return o;
}
启动redis,启动项目进行测试。
http://localhost:8080/saveuser2?id=1&username=deyou&password=1234 存储数据。
http://localhost:8080/getUser?id=1 查询数据,注意观察控制台查询语句。自行测试。
3. 使用 memcached
由于之前没接触过过memcached 数据库。
这里简单说一下安装过程,我的电脑系统是window10 ,直接去菜鸟教程找到了最新版本的压缩包
下载到电脑之后解压。
我们使用管理员身份执行以下命令将 memcached 添加来任务计划表中:
schtasks /create /sc onstart /tn memcached /tr "'c:\memcached\memcached.exe' -m 512"
**注意:**你需要使用真实的路径替代 c:\memcached\memcached.exe。
然后后双击memcached.exe 就开启服务了。
然后开始学习springboot memcache 缓存,过程和redis 差不多。这里需要写一个使用一个依赖
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
其他依赖是
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
新建一个MemcacheConfig 类来对数据库建立连接。(这个类十分重要。)
package com.zdy.springboot.config;
import net.spy.memcached.MemcachedClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.InetSocketAddress;
@Component
public class MemcachedConfig implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${memcache.ip}")
private String memcacheIp;
@Value("${memcache.port}")
private Integer memcachePort;
private MemcachedClient client = null;
@Override
public void run(String... args) throws Exception {
try {
client = new MemcachedClient(new InetSocketAddress(memcacheIp,memcachePort));
} catch (IOException e) {
logger.error("Connection to server failed",e);
}
logger.info("Connection to server Memcached success");
}
public MemcachedClient getClient() {
return client;
}
public Boolean set(String key,int time,String value) {
Boolean b = false;
try{//time 单位s
b=(this.getClient().set(key, time, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Boolean add(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().add(key, time, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object replace(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().replace(key, time, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object append(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().append(key, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object prepend(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().prepend(key, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object cas(String key,int time,String value){
return this.getClient().cas(key, time, value);
}
public Object get(String key){
return this.getClient().get(key);
}
public Boolean delete(String key){
Boolean b = false;
try{
b=(this.getClient().delete(key)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public long incr(String key,Integer value){
return this.getClient().incr(key,value);
}
public long decr(String key,Integer value){
return this.getClient().decr(key,value);
}
}
application.properties配置ip 地址和端口
# memcached 缓存机制配置
memcache.ip=localhost
memcache.port=11211
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 这里使用的是MySql 8.0.15 数据库,需要加时区,保证自己的数据库db_jdbc 存在。
spring.datasource.url=jdbc:mysql://localhost:3306/db_jpa?\
useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=666666
#JPA 配置
# validate加载Hibernate 时验证创建的数据库表
# create 每次加载hibernate,重新创建数据库表结构,这就是导致数据库丢失的原因
# drop-create 加载hibernate 时创建,退出时删除
# update 加载hibernate 时自动更新数据库
# none 启动时不做任何操作
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
# 控制台打印Sql
spring.jpa.show-sql=true
新建一个User 类,和上面一样,这里就不贴代码了
新建控制类。实现对象实例往Memcached 里的保存,查询,删除。
UserController.java
@RestController
public class UserController {
//memcached
@Resource
private MemcachedConfig memcachedConfig;
//mysq
@Autowired
private UserRepository userRepository;
//向memcached 里添加数据
//http://localhost:8080/saveUser?id=1&userName=deyou&userPassword=1234
@GetMapping(value = "saveUser")
public Boolean saveUser(Long id, String userName, String userPassword){
User user = new User(id, userName, userPassword);
return memcachedConfig.set(id.toString(), 1000,user.toString());
}
//向memcached 里查询数据
//http://localhost:8080/getUserById?id=1
@GetMapping(value = "getUserById")
public Object getUserById(Long id) {
return memcachedConfig.get(id.toString());
}
//添加删除数据
//http://localhost:8080/deleteCacheById?id=1
@GetMapping(value = "deleteCacheById")
public Boolean deleteCacheById(Long id) {
return memcachedConfig.delete(id.toString());
}
}
启动 Memcached 服务,启动项目,浏览器中输出对象方法上的地址,观察效果。
使用Memcahced 做缓存
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
在UserController 中插入如下代码
//mysq
@Autowired
private UserRepository userRepository;
//开始向mysql 数据库中添加数据
//http://localhost:8080/saveUser2?id=1&userName=deyou&userPassword=12345
@GetMapping("/saveUser2")
public User saveUser2(Long id, String userName, String userPassword) {
User user = new User(id, userName, userPassword);
System.out.println(user);
userRepository.save(user);
return user;
}
//查询数据,注意观察控制台信息
//http://localhost:8080/getUserById2?id=1
@GetMapping(value = "getUserById2")
public Object getUserById2(Long id) {
Object object = memcachedConfig.get(id.toString());
if (object == null) {
object = (userRepository.findById(id)).get();
if (object != null) {
//time 单位为s ,超过指定时间,memcached 数据丢失。
memcachedConfig.set(id.toString(), 10, object.toString());
}
}
return object;
}
启动服务器。重启项目。先执行http://localhost:8080/saveUser2?id=1&userName=deyou&userPassword=12345向数据库中插入一条数据,再访问http://localhost:8080/getUserById2?id=1第一个在控制台打出了SQL.因为没有缓存数据。第二次没有打印SQL,因为这次查询的是Memcached.
总结:这里简要总结一下redis 和Memcached 的区别
- 数据类型不同
- 数据一致性
- value 值得大小,redis 1GB ,Memcached 1M
- 存储方式,redis 支持数据持久化。Memcached 数据全部存在 内存中。
- 网络模型:Redis 是单线程IO复用模型,Memcached 是多线程IO 复用模型。
- 应用场景:Memcached 多用于缓存数据集,临时数据、session。而Redis 除了可以用缓存数据库外,还可以用作消息队列,数据堆栈。