一、新建项目,并添加所须要的jar包:javascript
分析:html
(1)在springboot中已经继承了Redis,就是说一旦咱们启动项目,它是能够根据properties配置文件中的配置信息自动建立Jedis对象的java
(2)咱们想要的是本身定义配置信息的名称,再根据本身定义的配置信息配置一个有关配置信息的类,最后根据这个类来建立一个咱们想要的Jedis对象。node
(3)springboot项目在加载的时候,因为springboot没法从配置文件中获取想要的信息,因此不会自动建立属于springboot的Jedis对象,而是根据咱们自定义mysql
的配置信息进行建立一个Jedis对象jquery
(4)在使用Jedis对象的时候,咱们给咱们的Jedis对象加上了@Bean注解,在要使用Jedis的类中使用@Autowired注解,因此spring中的BeanFactory工厂自动建立对象,web
,且是单例的;此处直接在@Autowire的注解下注入的是(3)中的对象,也就是根据咱们自定义信息建立的对象ajax
springboot-parent
spring-boot-web-start
mysql驱动包
druid(德鲁伊)链接池
springboot和mybatis的整合包
thymeleaf模板jar包
thymeleaf忽略语法的jar包
springboot和redis的整合包
jedis的包:
jedis就是经过Java实现对redis集群的增删改查,就是操做redis数据库
redis最终只支持5种数据格式:
String类型是最经常使用的
若是须要把User对象存入到redis中===>须要把User对象转换为json字符串===>再把字符串存入到redis中
json的jar包
configuration-processer包(为了使用其中的@ConfigurationProperties()注解,在下文中的RedisProperties类中所使用)
1 <!-- springboot的jar --> 2 <parent> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-parent</artifactId> 5 <version>1.5.10.RELEASE</version> 6 </parent> 7 8 <dependencies> 9 <!-- 10 springboot-starter-web 11 --> 12 <dependency> 13 <groupId>org.springframework.boot</groupId> 14 <artifactId>spring-boot-starter-web</artifactId> 15 </dependency> 16 <!-- 17 springboot-mybatis整合包 18 --> 19 <dependency> 20 <groupId>org.mybatis.spring.boot</groupId> 21 <artifactId>mybatis-spring-boot-starter</artifactId> 22 <version>1.3.0</version> 23 </dependency> 24 <!-- 25 mysql的驱动包 26 --> 27 <dependency> 28 <groupId>mysql</groupId> 29 <artifactId>mysql-connector-java</artifactId> 30 <version>5.1.38</version> 31 </dependency> 32 <!-- 33 druid链接池 34 --> 35 <dependency> 36 <groupId>com.alibaba</groupId> 37 <artifactId>druid</artifactId> 38 <version>1.1.10</version> 39 </dependency> 40 <!-- 41 html的thymeleaf模板 42 --> 43 <dependency> 44 <groupId>org.springframework.boot</groupId> 45 <artifactId>spring-boot-starter-thymeleaf</artifactId> 46 </dependency> 47 <!-- 48 redis的jar包以及jedis的jar包 49 --> 50 <dependency> 51 <groupId>redis.clients</groupId> 52 <artifactId>jedis</artifactId> 53 <version>2.9.0</version> 54 </dependency> 55 <!-- 56 redis和springboot的整合包 57 --> 58 <dependency> 59 <groupId>org.springframework.data</groupId> 60 <artifactId>spring-data-redis</artifactId> 61 </dependency> 62 <!-- 63 fastjson包 64 --> 65 <dependency> 66 <groupId>com.fasterxml.jackson.core</groupId> 67 <artifactId>jackson-databind</artifactId> 68 <version>2.8.1</version> 69 </dependency> 70 <!-- 71 添加springboot的进程jar包 72 里面包含了properties文件的读取(其实就是包含了@ConfigurationProperties()注解) 73 --> 74 <dependency> 75 <groupId>org.springframework.boot</groupId> 76 <artifactId>spring-boot-configuration-processor</artifactId> 77 <optional>true</optional> 78 </dependency> 79 80 <dependency> 81 <groupId>net.sourceforge.nekohtml</groupId> 82 <artifactId>nekohtml</artifactId> 83 <version>1.9.21</version> 84 </dependency> 85 </dependencies>
二、在springboot框架中,已经整合了Redis,能够直接使用。
此处根据springboot整合Redis的源码来实现本身的Redis,缘由:springboot自带的Redis已经很是完善,一旦出错,很难找到错误的地方
例如:当服务器的IP变更,端口号的问题等,能够经过在本身的配置类中打断点等方法判断错误的地方
三、在config中配置application.properties配置信息:
1 server.port=8081 2 server.context-path=/ 3 4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver 5 spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?userSSL=false 6 spring.datasource.username=root 7 spring.datasource.password=123456 8 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource 9 10 mybatis.type-aliases-package=com.aaa.liu.redis.model 11 mybatis.mapper-locations=classpath:mapper/*Mapper.xml 12 13 #开始自定义配置 14 # 配置redis集群的端口号和IP 15 spring.redis.nodes=192.168.134.130:6380,192.168.134.130:6381,192.168.134.130:6382,192.168.134.130:6383,192.168.134.130:6384,192.168.134.130:6385 16 # 配置了redistribution的最大链接数 17 spring.redis.maxAttempts=1000 18 # 配置了redis最大超时时间 19 spring.redis.commandTimeout=500000 20 # 配置了redis的失效时间 21 spring.redis.expire=1000 22 23 # 定义图书的key值 24 book_key=bookKey1 25 26 # 配置thymeleaf模板(不配置也能够,直接使用) 27 # 配置thymeleaf缓存:默认值true,须要手动修改成false 28 spring.thymeleaf.cache=false 29 # 配置不严谨的html 30 spring.thymeleaf.mode=LEGACYHTML5
四、在Java中新建一个config包,在包中新建RedisPro类,将刚刚application.properties的配置信息引入此类中:
1 package com.aaa.liu.redis.config; 2 3 import org.springframework.boot.context.properties.ConfigurationProperties; 4 import org.springframework.stereotype.Component; 5 6 /** 7 * @Author 刘其佳 8 * @DateTime 2019/8/28 19:49 9 * @Project_Name SpringBootRedis 10 * 11 * 一、@Component:把RedisProperties做为spring的一个组件 12 * 做用是 把Redis所须要配置和链接信息封装进RedisProperties类中 13 * 该组件可拆分 14 * 配置该组件类的做用是从application.properties文件中读取自定义的信息 15 * 二、@ConfigurationPrperties:做用是把application.properties中的属性读进RedisProperties类中 16 * 当使用该注解的时候必需要使用spring-boot-configuration-processor的jar包 17 * prefx="spring.redis";做用是选中在application.properties文件中的 属性名 进行对用(必须一致) 18 * 三、原本按理说在此类中定义属性事后,只有get方法,不该该有set方法,由于不能别其余类修改 19 * 但如果真的不放入set方法的话,在使用此类时,没法传递进来参数(属性是私有的) 20 * !!!因此最终仍是要有set方法,只要注意其余的类在调用RedisProperties类中的属性的时候, 21 * 必定不能修改!!! 22 * 23 */ 24 25 @Component 26 /** 27 * 全部以spring.redis开头的配置信息 28 */ 29 @ConfigurationProperties(prefix = "spring.redis") 30 public class RedisProperties { 31 32 /** 33 * 下列属性对应application.properties文件中的自定义的配置: 34 * redis集群的节点 35 * 最大链接数 36 * 最大超时时间 37 * 失效时间 38 */ 39 private String nodes; 40 private String maxAttempts; 41 private String commandTimeout; 42 private String expire; 43 44 public String getNodes() { 45 return nodes; 46 } 47 48 public void setNodes(String nodes) { 49 this.nodes = nodes; 50 } 51 52 public String getMaxAttempts() { 53 return maxAttempts; 54 } 55 56 public void setMaxAttempts(String maxAttempts) { 57 this.maxAttempts = maxAttempts; 58 } 59 60 public String getCommandTimeout() { 61 return commandTimeout; 62 } 63 64 public void setCommandTimeout(String commandTimeout) { 65 this.commandTimeout = commandTimeout; 66 } 67 68 public String getExpire() { 69 return expire; 70 } 71 72 public void setExpire(String expire) { 73 this.expire = expire; 74 } 75 }
五、仍然在config包中新建类:RedisConfiguration,将RedisProperties类注入进来:
1 package com.aaa.liu.redis.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 import redis.clients.jedis.HostAndPort; 9 import redis.clients.jedis.JedisCluster; 10 11 import java.util.HashSet; 12 import java.util.Set; 13 14 /** 15 * @Author 刘其佳 16 * @DateTime 2019/8/28 20:34 17 * @Project_Name SpringBootRedis 18 */ 19 20 /** 21 * 此处加上注解@Configuration或者@SpringBootApplication 22 * 23 * 由于在SpringBootApplication是一个组合注解 24 * 进入其中能够看到@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 25 * 再次进入@SpringBootConfiguration能够看到@Configuration 26 * 所以SpringBootConfiguration是EnableAutoConfiguration、ComponentScan和Configuration的组合注解 27 * 28 * 在较低版本的SpringBoot中: 29 * @Configuration是启用基于Java的配置 30 * @ComponentScan是启用组件扫描。 31 * @EnableAutoConfiguration是启用SpringBoot的自动配置功能 32 * 引用自:https://blog.csdn.net/qq_37939251/article/details/83058065 33 */ 34 35 /** 36 * 经过SpringBootApplication/Configuration注解将RedisConfiguration类标识为springboot的配置类 37 * 38 * 使用Jedis的jar包操做Redis集群数据库 39 * 一、Redis:标识了Redis是单节点模式(只有一台Redis) 40 * 源码中:protected static class RedisConfiguration {} 41 * 二、RedisCluster:标识了Redis的Redis集群模式 42 * 源码中:protected final RedisClusterConfiguration getClusterConfiguration() {} 43 * 本次只配置集群版的Redis 44 * 45 * @Bean: 至关于applicationContext.xml中的<bean id=""> class="JedisCluster.class"</bean> 46 * 47 * @EnableConfirgurationProperties({RedisProperties.class})是源码中使用的注解 48 * 在本类中咱们使用@Autowired来代替掉 49 */ 50 @SpringBootApplication 51 public class RedisConfiguration { 52 @Autowired 53 private RedisProperties redisProperties; 54 55 @Bean 56 public JedisCluster getJedisCluster(){ 57 //一、获取到application.properties文件中的Redis集群的节点信息 58 String nodes=redisProperties.getNodes(); 59 //二、使用split来将多个node进行拆分 60 String[] nodeArray = nodes.split(","); 61 //三、建立一个set集合,以HostAndPort对象做为泛型 62 Set<HostAndPort> hostAndPortSet=new HashSet<HostAndPort>(); 63 //四、遍历刚刚获得的节点数组 64 for (String node : nodeArray) { 65 //五、继续使用split将node拆分红IP和端口号(host、port) 66 String[] hostAndPortArray = node.split(":"); 67 //六、建立HostAndPort对象,并将获得的IP和端口号做为构造方法的参数传入 68 HostAndPort hostAndPort=new HostAndPort(hostAndPortArray[0],Integer.parseInt(hostAndPortArray[1])); 69 //七、把每个HostAndPort对象装进Set集合中 70 hostAndPortSet.add(hostAndPort); 71 } 72 //八、返回一个JedisCluster对象 73 return new JedisCluster(hostAndPortSet,Integer.parseInt(redisProperties.getCommandTimeout()),Integer.parseInt(redisProperties.getMaxAttempts())); 74 } 75 }
六、在Service层中新建RedisService
1 package com.aaa.liu.redis.service; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Service; 5 import redis.clients.jedis.JedisCluster; 6 7 /** 8 * @Author 刘其佳 9 * @DateTime 2019/8/28 21:39 10 * @Project_Name SpringBootRedis 11 * 在之前的UserService中注入UserMapper,对数据库进行操做 12 * 如今的RedisService中注入JedisCluster,对Redis数据库进行操做 13 */ 14 @Service 15 public class RedisService { 16 17 @Autowired 18 private JedisCluster jedisCluster; 19 20 /** 21 * 在Redis数据库中的增删查(没有修改)分别为: 22 * set del get 23 * 在下面分别定义 24 */ 25 26 /** 27 * @author 刘其佳 28 * @description 29 * 向Redis数据库中存入String类型的数据 30 * set的时候返回值是"ok" 31 * @param * param *:key 32 * @date 2019/8/28 33 * @return java.lang.String 34 * @throws 35 */ 36 public String set(String key,String value){ 37 return jedisCluster.set(key,value); 38 } 39 40 /** 41 * @author 刘其佳 42 * @description 43 * 从Redis数据库中经过key取出数据 44 * @param * param *:key 45 * @date 2019/8/28 46 * @return java.lang.String 47 * @throws 48 */ 49 public String get(String key){ 50 return jedisCluster.get(key); 51 } 52 53 /** 54 * @author 刘其佳 55 * @description 56 * 一、在此处的参数key是一个可变参 57 * 二、在Redis中删除的返回值是Integer类型 58 * 在Java中被转成了Long类型 59 * @param * param *:key 60 * @date 2019/8/28 61 * @return java.lang.Long 62 * @throws 63 */ 64 public Long del(String key){ 65 return jedisCluster.del(key); 66 } 67 68 /** 69 * @author 刘其佳 70 * @description 71 * 经过key设置失效时间(单位是秒) 72 * @param * param *:key 73 * param *:seconds 74 * @date 2019/8/28 75 * @return java.lang.Long 76 * @throws 77 */ 78 public Long expire(String key,Integer seconds){ 79 return jedisCluster.expire(key,seconds); 80 } 81 }
查询图书信息的service:
1 /** 2 * @author 刘其佳 3 * @description 4 * 注意:service层不容许注入service 5 * 由于在applicationContext.xml中配置事务后,事务都是在service中的 6 * 当service注入另外一个事务时,会形成事务串线(若是线程是并行的没有问题) 7 * applicationContext.xml配置文件中进行注解扫描: 8 * controller:必需要在springmvc中进行扫描 9 * service:必需要在spring中进行扫描 10 * 11 * @param * param *:null 12 * @date 2019/8/28 13 * @return 14 * @throws 15 */ 16 public Map<String ,Object> selectAllBooks(RedisService redisService){ 17 /** 18 * 一、从Redis中查询全部图书信息 19 * 二、判断从Redis中是否查询出数据 20 * 三、没有查询到的话再从数据库中查询数据 21 * 四、判断是否从数据库中查询到数据 22 * 五、若查询到数据,则返回结果,而且将数据放入Redis中 23 * 六、利用try...catch判断是否存入Redis中 24 * 七、若没有存入Redis中,要作出处理,此处为再次往Redis中存放一次 25 */ 26 Map<String,Object> resultMap=new HashMap<String, Object>(); 27 //一、 28 String bookString=redisService.get(bookKey); 29 //二、 30 if(null==bookString||"".equals(bookString)){ 31 //三、 32 List<Book> bookList = bookMapper.selectAll(); 33 //四、 34 if(bookList.size()>0){ 35 //六、 36 try { 37 //五、 38 redisService.set(bookKey, JSONUtil.toJsonString(bookList)); 39 //将从数据库中查询到的数据存入resultMap中 40 resultMap.put(StatusEnum.SUCCESS.getCodeName(), StatusEnum.SUCCESS.getCode()); 41 resultMap.put(StatusEnum.SUCCESS.getResultName(),bookList); 42 } catch (Exception e) { 43 //七、 44 redisService.set(bookKey, JSONUtil.toJsonString(bookList)); 45 //若是仍是没有将数据放入Redis中,此时咱们将resultMap里面的code换成404,可是用户仍是可以查询到数据 46 resultMap.put(StatusEnum.FAILED.getCodeName(),StatusEnum.FAILED.getCode()); 47 resultMap.put(StatusEnum.FAILED.getResultName(),bookList); 48 e.printStackTrace(); 49 return resultMap; 50 } 51 }else { 52 //说明数据库中没有数据,放入500 53 resultMap.put(StatusEnum.ERROR.getCodeName(),StatusEnum.ERROR.getCode()); 54 } 55 }else { 56 //!!!可能会存在Redis中的数据与数据库中的数据不一致的状况 57 //因此此处从Redis中获取到数据后与从数据库中查询到的数据作出一个判断(长度是否一致) 58 List<Book> bookList = JSONUtil.toList(bookString, Book.class); 59 List<Book> bookList1 = bookMapper.selectAll(); 60 if(bookList.size()==bookList1.size()){ 61 //将查询从Redis中查询到的数据存入到resultMap 62 //由于是从Redis中查询到的数据,此处给转回正常状态下的List集合类型的数据, 63 //正好从数据库中查询到的数据也是List,保持一致 64 resultMap.put(StatusEnum.SUCCESS.getCodeName(), StatusEnum.SUCCESS.getCode()); 65 resultMap.put(StatusEnum.SUCCESS.getResultName(),JSONUtil.toList(bookString,Book.class)); 66 }else { 67 //发现数据不一致时,丢弃Redis中的数据,并将数据库中的数据存入Redis中 68 redisService.set(bookKey,JSONUtil.toJsonString(bookList)); 69 resultMap.put(StatusEnum.SUCCESS.getCodeName(), StatusEnum.SUCCESS.getCode()); 70 resultMap.put(StatusEnum.SUCCESS.getResultName(),bookList1); 71 } 72 73 } 74 return resultMap; 75 }
七、控制层的BookController:
1 /** 2 * @Author 刘其佳 3 * @DateTime 2019/8/28 22:25 4 * @Project_Name SpringBootRedis 5 */ 6 @RestController 7 public class BookController { 8 9 @Autowired 10 private BookService bookService; 11 @Autowired 12 private RedisService redisService; 13 14 @RequestMapping("/") 15 public List<Book> selectAllBooks(){ 16 Map<String, Object> resultMap = bookService.selectAllBooks(redisService); 17 List<Book> bookList=null; 18 //若是查询到的resultMap中的code值为200,说明直接从Redis中查到数据了 19 if(StatusEnum.SUCCESS.getCode().equals((resultMap.get(StatusEnum.SUCCESS.getCodeName())))){ 20 bookList= (List<Book>) resultMap.get(StatusEnum.SUCCESS.getResultName()); 21 System.out.println("everything is ok!"); 22 }else { 23 //若是code值不为200,则有两种状况:404即Redis错误; 500即mysql错误 24 if(StatusEnum.ERROR.getCode().equals((resultMap.get(StatusEnum.SUCCESS.getCodeName())))){ 25 //一、result到最后也没存进去值,数据库都出现了问题 26 System.out.println("数据库出现了问题"); 27 }else{ 28 //二、result有值,Redis出现了问题,但从数据库查询了数据 29 bookList= (List<Book>) resultMap.get(StatusEnum.SUCCESS.getResultName()); 30 System.out.println("redis出现了问题"); 31 } 32 } 33 return bookList; 34 }
在本次项目中为了不魔法值问题,使用了枚举:
新建一个包statuscode ,在其中新建类:StatusEnum
1 package com.aaa.liu.redis.statuscode; 2 3 /** 4 * @Author 刘其佳 5 * @DateTime 2019/8/29 20:08 6 * @Project_Name SpringBootRedis 7 */ 8 public enum StatusEnum { 9 SUCCESS(200,"操做失败","code","result"), 10 FAILED(404,"操做成功","code","result"), 11 ERROR(500,"操做失败","code","result"); 12 13 private Integer code; 14 private String msg; 15 private String codeName; 16 private String resultName; 17 18 /** 19 * 给枚举赋值 20 * @param code 21 * @param msg 22 * @param codeName 23 */ 24 StatusEnum(Integer code, String msg, String codeName,String resultName) { 25 this.code = code; 26 this.msg = msg; 27 this.codeName = codeName; 28 this.resultName=resultName; 29 } 30 31 public Integer getCode() { 32 return code; 33 } 34 35 public void setCode(Integer code) { 36 this.code = code; 37 } 38 39 public String getMsg() { 40 return msg; 41 } 42 43 public void setMsg(String msg) { 44 this.msg = msg; 45 } 46 47 public String getCodeName() { 48 return codeName; 49 } 50 51 public void setCodeName(String codeName) { 52 this.codeName = codeName; 53 } 54 55 public String getResultName() { 56 return resultName; 57 } 58 59 public void setResultName(String resultName) { 60 this.resultName = resultName; 61 } 62 }
mapper中的BookMapper查询全部图书:
1 /** 2 * @Author 刘其佳 3 * @DateTime 2019/8/27 20:19 4 * @Project_Name springbootshiro 5 */ 6 @Mapper 7 public interface BookMapper { 8 //查询全部书籍 9 @Select("select id,book_name bookName,book_price bookPrice from book ") 10 List<Book> selectAll(); 11 }
其中的注意事项已在代码中写出!
此处插入一个关于ajax的用法:
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml" 3 xmlns:th="http://www.thymeleaf.org" 4 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> 5 <head> 6 <meta charset="UTF-8"> 7 <title>Index</title> 8 <script type="text/javascript" src="/jquery-3.2.1.min.js"></script> 9 </head> 10 11 <script> 12 /** 13 * 1.页面自动加载完成后,函数自动执行,查询出数据并拼接处table表单,将数据放入其中 14 * */ 15 $(document).ready( selectBookTest()); 16 17 function selectBookTest(){ 18 $.ajax({ 19 type:"POST", 20 url:"selectBooks", 21 dataType:"JSON", 22 success:function (msg) { 23 var tr=""; 24 for(var i=0;i<msg.length;i++){ 25 tr+="<tr><td>"+msg[i].id+"</td>"; 26 tr+="<td>"+msg[i].bookName+"</td>"; 27 tr+="<td>"+msg[i].bookPrice+"</td></tr>"; 28 //不能直接使用下面的方式,不然会只有一行数据;要使用+= 29 // tr="<tr><td>"+msg[i].id+"</td><td>"+msg[i].bookName+"</td><td>"+msg[i].bookPrice+"</td></tr>" 30 } 31 tr+="<tr><td onclick='testBookAdd()'>添加</td></tr>"; 32 $("#test_Book").append("<table border='1px solid black'>" + 33 "<thead>" + 34 "<tr >" + 35 "<th>图书编号</th>" + 36 "<th>图书名称</th>" + 37 "<th>图书价格</th>" + 38 "</tr>" + 39 tr + 40 "</thead>" + 41 "</table>") 42 } 43 44 }) 45 } 46 47 /** 48 * 2.点击添加按钮,将查询所得的数据清空,并拼接出新的form表单用以输入要添加的数据 49 */ 50 function testBookAdd() { 51 $("#test_Book").empty(); 52 $("#test_Book").append("<form id='formBook'><table border='1px solid red'>" + 53 "<tr><td>图书名称:</td><td><input type='text' name='bookName'/></td></tr>" + 54 "<tr><td>图书价格:</td><td><input type='text' name='bookPrice'/></td></tr>"+ 55 "<tr><td><input type='button' value='submit' onclick='bookAdd()'/></td></tr>"+ 56 "</table></form>") 57 } 58 59 /** 60 * 3.输入完数据,使用ajax调用添加方法添加 61 */ 62 function bookAdd() { 63 $.ajax({ 64 type:"POST", 65 url:"testAddBook", 66 data:$("#formBook").serialize(), 67 dataType:"json", 68 success:function (msg) { 69 if(null==msg){ 70 alert("添加失败") 71 }else{ 72 $("#test_Book").empty(); 73 selectBookTest(); 74 } 75 } 76 }) 77 } 78 79 </script> 80 <body> 81 82 <div id="test_Book"></div> 83 </body> 84 85 86 </html>