how2j.cn

下载区
文件名 文件大小
springboot.rar 25k
步骤 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   

Redis 是一套 key-value 高性能数据库。
关于 Redis 如何安装,运用有专门的教程,在这里就不展开了。
Redis 教程
步骤 2 :

运行 Redis 服务器

edit
按照 Redis 如何运行 中的做法,把 Redis 服务器先跑起来,这个不跑起来,本教程就没法撸了。
运行 Redis 服务器
步骤 3 :

基于前面的知识点

edit
本教程在前面的知识点:SPRINGBOOT使用JPA实现完整的CRUD和分页 的基础上进行扩展。 所以没有撸前面的同学,请先撸了,不然这里也撸不出来,因为数据库怎么建立都是在前面写的,你没做,后面也没法做的。
步骤 4 :

先运行,看到效果,再学习

edit
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
启动后,访问如下地址

http://127.0.0.1:8080/listCategory

可以看到如图所示的 CRUD 和分页的效果。
虽然,在使用效果上,看起来和 SPRINGBOOT使用JPA实现完整的CRUD和分页 没有任何分别,但是在后台,已经把数据都撸到 Redis 里面缓存起来了。
先运行,看到效果,再学习
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。

推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
接下来,就一条条地说,在原来项目的基础上做了什么改动,以达到这样的效果
增加对 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

edit
增加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
增加注解,以开启缓存

@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); } }
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

edit
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 = new Jackson2JsonRedisSerializer(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; } }
创建一个工具类 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; } }
增加 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); }
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

edit
实现类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删除掉所有的缓存就好了。
CategoryServiceImpl
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

edit
由原来直接从 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") int start,@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"; } }


HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。


问答区域    
2022-08-22 不创建Page4Navigator,而是通过page获取到一个List<Category>,好像也能解决问题。这样操作有什么弊端吗
somehow

如题







回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到




2022-03-25 spring2.0以上的配置(成功运行,先跑起来再说)
dyf137




依赖和yml文件和站长的有所差别。 先跑起来再说 然后impl类里有两个方法有红色下划线,我把findOne改成getOne了,delete改为了deleteById(没有标红请忽略)
<!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>



 #redis
  redis:
    database: 0
    host: localhost
    port: 6379
    password:
    timeout: 30s
    lettuce:
      pool:
        max-active: -1
        max-idle: 8
        max-wait: -1s
        min-idle: 0
      shutdown-timeout: 100ms

							


1 个答案

dyf137
答案时间:2022-03-25
RedisConfig也不一样



回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到





2022-03-06 为什么配置了数据库,还是报数据库错误
2021-11-24 【解决】【SpringBoot 2+ IDEA + Restful 风格】#p0等含义的意义
2021-08-13 搞不定


提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 26 条以前的提问,请 点击查看

提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
关于 JAVA 框架-SpringBoot-Redis 的提问

尽量提供截图代码异常信息,有助于分析和解决问题。 也可进本站QQ群交流: 578362961
提问尽量提供完整的代码,环境描述,越是有利于问题的重现,您的问题越能更快得到解答。
对教程中代码有疑问,请提供是哪个步骤,哪一行有疑问,这样便于快速定位问题,提高问题得到解答的速度
在已经存在的几千个提问里,有相当大的比例,是因为使用了和站长不同版本的开发环境导致的,比如 jdk, eclpise, idea, mysql,tomcat 等等软件的版本不一致。
请使用和站长一样的版本,可以节约自己大量的学习时间。 站长把教学中用的软件版本整理了,都统一放在了这里, 方便大家下载: https://how2j.cn/k/helloworld/helloworld-version/1718.html

上传截图