一. 缓存数据到电脑内存
使用Redis前,我们先新建一个SpringBoot项目,演示一下如何使用Spring的缓存将数据缓存在电脑的内存中
首先做以下数据准备。
1. 添加相关依赖
<dependencies> <!--JDBC API--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--Mybatis Framework--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!--MysqlDriver--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> <scope>runtime</scope> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
2. 配置yml(接下来的项目中,我们将会使用Mybatis-plus,所以还需要配置Mybatis-plus)
spring: datasource: url: jdbc:mysql://localhost:3306/demo2?useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver username: root password: zhangjia jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: io.zhangjia.redis.entity configuration: map-underscore-to-camel-case: true mybatis-plus: mapper-locations: classpath:mapper/*.xml type-aliases-package: io.zhangjia.redis.entity configuration: map-underscore-to-camel-case: true server: port: 8888 logging: level: io.zhangjia.redis: trace file: E:\logs\zhangjia.log
3. 新建数据表,并随便插入几条数据
create table book ( id int(6) auto_increment primary key, name varchar(100), author varchar(100), price double(6, 2), status int default 1, create_time timestamp default CURRENT_TIMESTAMP );
2. 新建Book表对应的实体类
package io.zhangjia.redis.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import java.sql.Timestamp; public class Book{ @TableId(value = "id",type = IdType.AUTO) private Integer id; private String name; private String author; private Double price; private Integer status; private Timestamp createTime; //省略getter和setter }
3. 新建mapper
package io.zhangjia.redis.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import io.zhangjia.redis.entity.Book; import org.springframework.stereotype.Component; @Component public interface BookMapper extends BaseMapper<Book> { }
这里我们使用Mybatis-plus内置的通用Mapper,所以只需要创建上面的接口即可,mapper.xml无需创建。
4. 新建Service
因为使用Redis需要给Service中的方法添加注解,所以该项目的Service不能再使用内置的通用Service,需要我们手动编写。
package io.zhangjia.redis.service; import io.zhangjia.redis.entity.Book; import java.util.List; public interface BookService { List<Book> getBookList(); Book getOne(Integer id); Book editBook(Book book); boolean delBook(Integer id); } //BookService的实现类 package io.zhangjia.redis.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import io.zhangjia.redis.entity.Book; import io.zhangjia.redis.mapper.BookMapper; import io.zhangjia.redis.service.BookService; import org.springframework.beans.factory.annotation.Autowired; 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.List; @Service("bookService") public class BookServiceImpl implements BookService { @Autowired private BookMapper bookMapper; @Override public List<Book> getBookList() { return bookMapper.selectList(null); } @Override public Book getOne(Integer id) { return bookMapper.selectById(id); } @Override public Book editBook(Book book) { int i = bookMapper.updateById(book); return bookMapper.selectById(book.getId()); } @Override public boolean delBook(Integer id) { return bookMapper.deleteById(id) == 1; } }
5. 新建Controller
package io.zhangjia.redis.controller; import io.zhangjia.redis.entity.Book; import io.zhangjia.redis.service.BookService; import io.zhangjia.redis.util.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController public class BookController { @Autowired private BookService bookService; @GetMapping("/books") @ResponseBody public List<Book> books() { return bookService.getBookList(); } @GetMapping("/book/{id}") public Book getBook(@PathVariable Integer id) { System.out.println("id = " + id); return bookService.getOne(id); } @DeleteMapping("/book/{id}") public Result delBook(@PathVariable Integer id) { System.out.println("id = " + id); boolean b = bookService.delBook(id); return new Result(b?"success":"error"); } @PutMapping("/book") public Book editBook(Book book) { System.out.println("book = " + book); return bookService.editBook(book); } }
到这里我们的准备功能就全部完成了,上述代码的功能就是对Book表进行一些简单的增删改查。以查询全部某一本书为例,我们在postman中访问http://localhost:8888/book/5,结果如下:
打开IDEA的控制台,也能看到相关的日志信息,因为此时还没有做任何缓存操作,所以当我们多次访问http://localhost:8888/book/5时,每一次都会执行查询语句:
那么如何开启缓存呢?
二. 开启缓存
首先在RedisApplication.java中添加@EnableCaching注解
package io.zhangjia.redis; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @MapperScan("io.zhangjia.redis.mapper") @EnableCaching public class RedisApplication { public static void main(String[] args) { SpringApplication.run(RedisApplication.class, args); } }
当你在配置类(@Configuration)上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。[1]
接下来在BookService中,为getOne方法添加@Cacheable注解:
@Override //Cacheable当缓存中已经存在该数据, 那么直接从缓存中读取数据,如果缓存中不存在,那么将返回结果写入缓存 @Cacheable(cacheNames = "book",key = "'book-' + #id") public Book getOne(Integer id) { return bookMapper.selectById(id); }
其中cacheNames 意为,而key,因为相同的key会覆盖,所以这里我们以book-为前缀,以每本书的id为后缀来唯一区分一本书。
此时重启项目,再次访问http://localhost:8888/book/5,无论访问多少次,只有第一次会执行SELECT语句,缓存已经生效
虽然说缓存成功,但是也会产生新的问题:
假如我们现在对id为5的这本书执行了修改或者删除操作,虽然说数据库里的数据修改成功了,但是因为我们之前对该书进行了缓存,所以当我们访问http://localhost:8888/book/5的时候,显示的还是未修改或者未删除前的数据,为了解决该问题,我们需要为修改和删除的方法分别添加对应的注解:
package io.zhangjia.redis.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import io.zhangjia.redis.entity.Book; import io.zhangjia.redis.mapper.BookMapper; import io.zhangjia.redis.service.BookService; import org.springframework.beans.factory.annotation.Autowired; 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.List; @Service("bookService") public class BookServiceImpl implements BookService { @Autowired private BookMapper bookMapper; @Override //list一般不缓存 public List<Book> getBookList() { return bookMapper.selectList(null); } @Override //Cacheable当缓存中已经存在该数据, 那么直接从缓存中读取数据,如果缓存中不存在,那么将返回结果写入缓存 //unless:不进行缓存的条件 @Cacheable(cacheNames = "book",key = "'book-' + #id",unless = "#result == null") public Book getOne(Integer id) { return bookMapper.selectById(id); } @Override //CachePut始终会将方法的返回值写入缓存 @CachePut(cacheNames = "book",key = "'book-' + #book.id") public Book editBook(Book book) { int i = bookMapper.updateById(book); return bookMapper.selectById(book.getId()); } @Override @CacheEvict(cacheNames = "book",key = "'book-' + #id") public boolean delBook(Integer id) { return bookMapper.deleteById(id) == 1; } }
三. 使用Redis进行缓存
在上一段中,我们的数据都是缓存在电脑的内存中,项目一重启就都没了,我们可以使用redis缓存我们的数据。
在SpringBoot中使用Redis非常简单,只需要两个操作即可:
1. 添加依赖
以SpringBoot项目为例,使用Redis需要先在pom.xml文件中添加以下依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2. 配置Redis
在application.yml中配置Redis的host和端口号
spring: redis: host: 192.168.112.129 port: 6379 #其实不加这个也可以,默认就是6379 database: 0 #其实不加这个也可以,默认就是存入第0个
此时再次访问http://localhost:8888/book/5,缓存的数据已经存入Redis,即使重启项目,缓存的数据也不会消失
java自带的序列化器将缓存数据序列化成了上图红框中的格式,但是这种格式并不利于我们查看,我们可以自定义Redis的序列化容器来将缓存的数据序列化成json的格式,新建对应的配置类即可:
package io.zhangjia.redis.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @Configuration public class SerializableConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { //准备key的序列化器 RedisSerializationContext.SerializationPair<String> keySerializer = RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()); //准备value的序列化器 RedisSerializationContext.SerializationPair<Object> valueSerializer = RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()); RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(keySerializer) //设置key的序列化器 .serializeValuesWith(valueSerializer) //设置value的序列化器 .entryTtl(Duration.ofDays(1)); //设置有效期为1天 // .disableCachingNullValues(); //不允许缓存空值当null值进行缓存时,会出现异常 RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory) .cacheDefaults(cacheConfig).build(); return cacheManager; } }
此时将Redis中的数据清空(一定要记得清空,否则会报JsonParseException异常),再次访问http://localhost:8888/book/5,可以看到缓存中的数据已经是json格式:
在SSM项目中,如果使用的不是jackson,而是fastjson,那么需要这样配置:
先添加fastjson依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.59</version> </dependency>
接下来将配置类的value的序列化器修改为以下内容即可:
//jackson, RedisSerializationContext.SerializationPair<Object> valueSerializer = RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()); //将上面的内容修改为: //fastjson: //RedisSerializationContext.SerializationPair<Object> valueSerializer = RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer());
三. Redis测试类
最后给出redis的一个测试类,里面包含了几个常用的方法,了解即可。
package io.zhangjia.redis; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class RedisApplicationTests { @Autowired private RedisTemplate redisTemplate; //注入redisTemplate @ Test public void contextLoads() { RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); String ping = connection.ping(); System.out.println("ping = " + ping); //如果结果是PONG,说明redis正常工作 String key = "name"; String value = "zhangjia"; //存储数据到redis Boolean set = connection.set(key.getBytes(), value.getBytes()); System.out.println("存入是否成功?" + (set ? "是" : "否")); //根据key获取数据库中的数据 byte[] name = connection.get(key.getBytes()); System.out.println("name = " + new String(name)); /* * 输出: * ping = PONG * 存入是否成功?是 * name = zhangjia * */ } @ Test public void contextLoads2() { //给单个数据设置过期时间的两种方法 // ValueOperations<String, String> stringValueOperations = stringRedisTemplate.opsForValue(); // stringValueOperations.set("code","7896", Duration.ofSeconds(10)); //60秒后过期 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set("codes", "7895", Duration.ofSeconds(20)); } }
请登录之后再进行评论