步骤1:Redis步骤2:运行 Redis 服务器步骤3:基于前面的知识点步骤4:先运行,看到效果,再学习步骤5:模仿和排错步骤6:改动步骤7:pom.xml步骤8:application.properties步骤9:Application步骤10:RedisConfig.java步骤11:Page4Navigator步骤12:CategoryService步骤13:CategoryServiceImpl步骤14:CategoryController
步骤 1 : Redis
Redis 是一套 key-value 高性能数据库。
关于 Redis 如何安装,运用有专门的教程,在这里就不展开了。
Redis 教程步骤 2 : 运行 Redis 服务器
按照 Redis 如何运行 中的做法,把 Redis 服务器先跑起来,这个不跑起来,本教程就没法撸了。
步骤 3 : 基于前面的知识点
本教程在前面的知识点:SPRINGBOOT使用JPA实现完整的CRUD和分页 的基础上进行扩展。 所以没有撸前面的同学,请先撸了,不然这里也撸不出来,因为数据库怎么建立都是在前面写的,你没做,后面也没法做的。步骤 4 : 先运行,看到效果,再学习
老规矩,先下载下载区(点击进入)的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
启动后,访问如下地址http://127.0.0.1:8080/listCategory
可以看到如图所示的 CRUD 和分页的效果。
虽然,在使用效果上,看起来和 SPRINGBOOT使用JPA实现完整的CRUD和分页 没有任何分别,但是在后台,已经把数据都撸到 Redis 里面缓存起来了。
步骤 5 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程步骤 6 : 改动
接下来,就一条条地说,在原来项目的基础上做了什么改动,以达到这样的效果步骤 7 : pom.xml
增加对 Redis 支持的包<?xml
version="1.0"
encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.how2java</groupId>
<artifactId>springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot</name>
<description>springboot</description>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- servlet依赖. -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- tomcat的支持.-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<!-- 这个需要为 true 热部署才有效 -->
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<!-- jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version> 4.12</version>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
步骤 8 : application.properties
增加redis相关配置
同时让hibernate的sql语句显示出来,这样才知道到底是通过 Redis 取到的数据,还是依然是从数据库取到的数据spring.jpa.show-sql=true
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
###########################redis#########################
#Redis数据库索引(默认为0)
spring.redis.database=0
#Redis服务器地址
spring.redis.host=127.0.0.1
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
spring.redis.password=
#连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=10
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.pool.max-idle=8
#连接池中的最小空闲连接
spring.redis.pool.min-idle=0
#连接超时时间(毫秒)
spring.redis.timeout=0
spring.jpa.show-sql=true
步骤 9 : Application
增加注解,以开启缓存@EnableCaching
package
com.how2java.springboot;
import
org.springframework.boot.SpringApplication;
import
org.springframework.boot.autoconfigure.SpringBootApplication;
import
org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public
class
Application {
public
static
void
main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
步骤 10 : RedisConfig.java
Redis 缓存配置类。
这个配置,一个作用: 让保存到 Redis 里的 key 和 value 都转换为可读的 json 格式。 否则会是二进制格式,通过RedisClient 工具也无法识别。package
com.how2java.springboot.config;
import
org.springframework.cache.CacheManager;
import
org.springframework.cache.annotation.CachingConfigurerSupport;
import
org.springframework.cache.annotation.EnableCaching;
import
org.springframework.context.annotation.Bean;
import
org.springframework.context.annotation.Configuration;
import
org.springframework.data.redis.cache.RedisCacheManager;
import
org.springframework.data.redis.core.RedisTemplate;
import
org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import
org.springframework.data.redis.serializer.RedisSerializer;
import
org.springframework.data.redis.serializer.StringRedisSerializer;
import
com.fasterxml.jackson.annotation.JsonAutoDetect;
import
com.fasterxml.jackson.annotation.PropertyAccessor;
import
com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
@EnableCaching
//Redis 缓存配置类
public
class
RedisConfig
extends
CachingConfigurerSupport {
@Bean
public
CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {
RedisSerializer stringSerializer =
new
StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
newJackson2JsonRedisSerializer(Object.class);
ObjectMapper om =
new
ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
CacheManager cacheManager =
new
RedisCacheManager(redisTemplate);
return
cacheManager;
}
}
步骤 11 : Page4Navigator
创建一个工具类 Page4Navigator 用以替换 原本分页查询要返回的 org.springframework.data.domain.Page 类。 原因是 Page 类对json 还原不支持,在放如 Redis 之后,再拿出来,就会报错失败。
使用 Page4Navigator 对其包裹,就解决了这个问题了。package
com.how2java.springboot.util;
import
java.util.List;
import
org.springframework.data.domain.Page;
public
class
Page4Navigator<T> {
Page<T> page4jpa;
int
navigatePages;
int
totalPages;
int
number;
long
totalElements;
int
size;
int
numberOfElements;
List<T> content;
boolean
isHasContent;
boolean
first;
boolean
last;
boolean
isHasNext;
boolean
isHasPrevious;
int[] navigatepageNums;
public
Page4Navigator() {
//这个空的分页是为了 Redis 从 json格式转换为 Page4Navigator 对象而专门提供的
}
public
Page4Navigator(Page<T> page4jpa,int
navigatePages) {
this.page4jpa = page4jpa;
this.navigatePages = navigatePages;
totalPages = page4jpa.getTotalPages();
number = page4jpa.getNumber() ;
totalElements = page4jpa.getTotalElements();
size = page4jpa.getSize();
numberOfElements = page4jpa.getNumberOfElements();
content = page4jpa.getContent();
isHasContent = page4jpa.hasContent();
first = page4jpa.isFirst();
last = page4jpa.isLast();
isHasNext = page4jpa.hasNext();
isHasPrevious = page4jpa.hasPrevious();
calcNavigatepageNums();
}
private
void
calcNavigatepageNums() {
int
navigatepageNums[];
int
totalPages = getTotalPages();
int
num = getNumber();
//当总页数小于或等于导航页码数时
if
(totalPages <= navigatePages) {
navigatepageNums =
new
int[totalPages];
for
(int
i =
0; i < totalPages; i++) {
navigatepageNums[i] = i +
1;
}
}
else
{
//当总页数大于导航页码数时
navigatepageNums =
new
int[navigatePages];
int
startNum = num - navigatePages /
2;
int
endNum = num + navigatePages /
2;
if
(startNum <
1) {
startNum =
1;
//(最前navigatePages页
for
(int
i =
0; i < navigatePages; i++) {
navigatepageNums[i] = startNum++;
}
}
else
if
(endNum > totalPages) {
endNum = totalPages;
//最后navigatePages页
for
(int
i = navigatePages -
1; i >=
0; i--) {
navigatepageNums[i] = endNum--;
}
}
else
{
//所有中间页
for
(int
i =
0; i < navigatePages; i++) {
navigatepageNums[i] = startNum++;
}
}
}
this.navigatepageNums = navigatepageNums;
}
public
int
getNavigatePages() {
return
navigatePages;
}
public
void
setNavigatePages(int
navigatePages) {
this.navigatePages = navigatePages;
}
public
int
getTotalPages() {
return
totalPages;
}
public
void
setTotalPages(int
totalPages) {
this.totalPages = totalPages;
}
public
int
getNumber() {
return
number;
}
public
void
setNumber(int
number) {
this.number = number;
}
public
long
getTotalElements() {
return
totalElements;
}
public
void
setTotalElements(long
totalElements) {
this.totalElements = totalElements;
}
public
int
getSize() {
return
size;
}
public
void
setSize(int
size) {
this.size = size;
}
public
int
getNumberOfElements() {
return
numberOfElements;
}
public
void
setNumberOfElements(int
numberOfElements) {
this.numberOfElements = numberOfElements;
}
public
List<T> getContent() {
return
content;
}
public
void
setContent(List<T> content) {
this.content = content;
}
public
boolean
isHasContent() {
return
isHasContent;
}
public
void
setHasContent(boolean
isHasContent) {
this.isHasContent = isHasContent;
}
public
boolean
isFirst() {
return
first;
}
public
void
setFirst(boolean
first) {
this.first = first;
}
public
boolean
isLast() {
return
last;
}
public
void
setLast(boolean
last) {
this.last = last;
}
public
boolean
isHasNext() {
return
isHasNext;
}
public
void
setHasNext(boolean
isHasNext) {
this.isHasNext = isHasNext;
}
public
boolean
isHasPrevious() {
return
isHasPrevious;
}
public
void
setHasPrevious(boolean
isHasPrevious) {
this.isHasPrevious = isHasPrevious;
}
public
int[] getNavigatepageNums() {
return
navigatepageNums;
}
public
void
setNavigatepageNums(int[] navigatepageNums) {
this.navigatepageNums = navigatepageNums;
}
}
步骤 12 : CategoryService
增加 Service接口。 注意: list 返回的是 Page4Navigator 而不是 Page 类型。package
com.how2java.springboot.service;
import
org.springframework.data.domain.Pageable;
import
com.how2java.springboot.pojo.Category;
import
com.how2java.springboot.util.Page4Navigator;
public
interface
CategoryService {
public
Page4Navigator<Category> list(Pageable pageable);
public
void
save(Category category);
public
void
delete(int
id);
public
Category get(int
id);
}
步骤 13 : CategoryServiceImpl
实现类CategoryServiceImp 做了一下工作:
1. 实现 CategoryService 接口,提供 crud
2. 在相应方法实现的时候,都是通过调用 dao 实现的
3. CacheConfig,表示 分类数据在 redis 中都放在 category 这个分组里。@CacheConfig(cacheNames="category")
4. list方法讲解:
先说注解@Cacheable(key="'category '+#p0.offset + '-' + #p0.pageSize ")
假如是第一页,即offset=0,pageSize=5,那么会创建一个 key: "category 0-5"
首先根据这个key 到 redis中查询数据。 第一次是不会有数据的,那么就会从数据库中取到这5条数据,然后以这个 key: "category 0-5" 保存到 redis 数据库中。
下一次再次访问的时候,根据这个key,就可以从 redis 里取到数据了。
5. get 方法讲解
先说注解:@Cacheable(key="'category '+ #p0")
假如是获取id=71的数据,那么
就会以 key= "category 71" 到reids中去获取,如果没有就会从数据库中拿到,然后再以 key= "category 71" 这个值存放到 redis 当中。
下一次再次访问的时候,根据这个key,就可以从 redis 里取到数据了。
6. add 方法讲解
先说注解:@CacheEvict(allEntries=true)
// @CachePut(key="'category '+ #p0")
可以看到,本来有个 CachePut,但是被注释掉了。 按理说,本来是应该用这个的。 这样会到在,在增加数据之后,就会在Redis 中以 key= "category 71" 缓存一条数据。 但是为什么被注释掉不用呢?
因为加入这样做了,那么 list 对应的数据,在缓存在对应的数据,并没有发生变化呀? 因为 list 对应的数据是这样的 key: "category 0-5"。 如果用这种方式,就会导致数据不同步,即,虽然增加了,并且也增加到缓存中了,但是因为 key 不一样,通过查询拿到的数据,是不会包含新的这一条的。
所以,才会使用CacheEvict 这个注解,这个注解就表示清除掉缓存。 allEntries= true 是表示清除掉 category 分组 下所有的keys. 注意看截图,里面有一个 category~keys ,里面就表明了都有哪些 keys,都会被清除掉。
假如这个时候,还有一个分组 cacheNames="product", 那么它下面对应的缓存,都是不会被影响到的。 这样就保证了,只清楚当前分组下的缓存,而不是清除 redis 所有的数据了。
7. delete 方法讲解
先说注解:@CacheEvict(allEntries=true)
// @CacheEvict(key="'category '+ #p0")
这个道理和 add 是一样的,仅仅删除 key= "category 71" ,没有什么意义, key: "category 0-5" 里面的数据没有影响呀。 所以还是通过 CacheEvict删除掉所有的缓存就好了。
package
com.how2java.springboot.service.impl;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.cache.annotation.CacheConfig;
import
org.springframework.cache.annotation.CacheEvict;
import
org.springframework.cache.annotation.CachePut;
import
org.springframework.cache.annotation.Cacheable;
import
org.springframework.data.domain.Page;
import
org.springframework.data.domain.Pageable;
import
org.springframework.stereotype.Service;
import
com.how2java.springboot.dao.CategoryDAO;
import
com.how2java.springboot.pojo.Category;
import
com.how2java.springboot.service.CategoryService;
import
com.how2java.springboot.util.Page4Navigator;
@Service
@CacheConfig(cacheNames="category")
public
class
CategoryServiceImpl
implements
CategoryService{
@Autowired
CategoryDAO categoryDAO;
@Override
@Cacheable(key="'category '+#p0.offset + '-' + #p0.pageSize ")
public
Page4Navigator<Category> list(Pageable pageable) {
Page<Category> pageFromJPA= categoryDAO.findAll(pageable);
Page4Navigator<Category> page =
new
Page4Navigator<>(pageFromJPA,5);
return
page;
}
@Override
@Cacheable(key="'category '+ #p0")
public
Category get(int
id) {
Category c =categoryDAO.findOne(id);
return
c;
}
@Override
@CacheEvict(allEntries=true)
// @CachePut(key="'category '+ #p0")
public
void
save(Category category) {
// TODO Auto-generated method stub
categoryDAO.save(category);
}
@Override
@CacheEvict(allEntries=true)
// @CacheEvict(key="'category '+ #p0")
public
void
delete(int
id) {
// TODO Auto-generated method stub
categoryDAO.delete(id);
}
}
步骤 14 : CategoryController
由原来直接从 dao 获取,变为从 Service 获取了package
com.how2java.springboot.web;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.data.domain.PageRequest;
import
org.springframework.data.domain.Pageable;
import
org.springframework.data.domain.Sort;
import
org.springframework.stereotype.Controller;
import
org.springframework.ui.Model;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestParam;
import
com.how2java.springboot.pojo.Category;
import
com.how2java.springboot.service.CategoryService;
import
com.how2java.springboot.util.Page4Navigator;
@Controller
public
class
CategoryController {
@Autowired
CategoryService categoryService;
@RequestMapping("/listCategory")
public
String listCategory(Model m,@RequestParam(value =
"start", defaultValue =
"0")
intstart,@RequestParam(value =
"size", defaultValue =
"5")
int
size)
throws
Exception {
start = start<0?0:start;
Sort sort =
new
Sort(Sort.Direction.DESC,
"id");
Pageable pageable =
new
PageRequest(start, size, sort);
Page4Navigator<Category> page =categoryService.list(pageable);
m.addAttribute("page", page);
return
"listCategory";
}
@RequestMapping("/addCategory")
public
String addCategory(Category c)
throws
Exception {
categoryService.save(c);
return
"redirect:listCategory";
}
@RequestMapping("/deleteCategory")
public
String deleteCategory(Category c)
throws
Exception {
categoryService.delete(c.getId());
return
"redirect:listCategory";
}
@RequestMapping("/updateCategory")
public
String updateCategory(Category c)
throws
Exception {
categoryService.save(c);
return
"redirect:listCategory";
}
@RequestMapping("/editCategory")
public
String ediitCategory(int
id,Model m)
throws
Exception {
Category c= categoryService.get(id);
m.addAttribute("c", c);
return
"editCategory";
}
}
更多内容,点击了解: https://how2j.cn/k/springboot/springboot-redis/1789.html