1、新建项目,并添加所需要的jar包:
分析:
(1)在springboot中已经继承了Redis,就是说一旦我们启动项目,它是可以根据properties配置文件中的配置信息自动创建Jedis对象的
(2)我们想要的是自己定义配置信息的名称,再根据自己定义的配置信息配置一个有关配置信息的类,最后根据这个类来创建一个我们想要的Jedis对象。
(3)springboot项目在加载的时候,由于springboot无法从配置文件中获取想要的信息,所以不会自动创建属于springboot的Jedis对象,而是根据我们自定义
的配置信息进行创建一个Jedis对象
(4)在使用Jedis对象的时候,我们给我们的Jedis对象加上了@Bean注解,在要使用Jedis的类中使用@Autowired注解,所以spring中的BeanFactory工厂自动创建对象,
,且是单例的;此处直接在@Autowire的注解下注入的是(3)中的对象,也就是根据我们自定义信息创建的对象
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>
2、在springboot框架中,已经整合了Redis,可以直接使用。
此处根据springboot整合Redis的源码来实现自己的Redis,原因:springboot自带的Redis已经非常完善,一旦出错,很难找到错误的地方
例如:当服务器的IP变动,端口号的问题等,可以通过在自己的配置类中打断点等方法判断错误的地方
3、在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
4、在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 * 1、@Component:把RedisProperties作为spring的一个组件
12 * 作用是 把Redis所需要配置和连接信息封装进RedisProperties类中
13 * 该组件可拆分
14 * 配置该组件类的作用是从application.properties文件中读取自定义的信息
15 * 2、@ConfigurationPrperties:作用是把application.properties中的属性读进RedisProperties类中
16 * 当使用该注解的时候必须要使用spring-boot-configuration-processor的jar包
17 * prefx="spring.redis";作用是选中在application.properties文件中的 属性名 进行对用(必须一致)
18 * 3、本来按理说在此类中定义属性过后,只有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 }
5、仍然在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 * 1、Redis:标识了Redis是单节点模式(只有一台Redis)
40 * 源码中:protected static class RedisConfiguration {}
41 * 2、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 //1、获取到application.properties文件中的Redis集群的节点信息
58 String nodes=redisProperties.getNodes();
59 //2、使用split来将多个node进行拆分
60 String[] nodeArray = nodes.split(",");
61 //3、创建一个set集合,以HostAndPort对象作为泛型
62 Set<HostAndPort> hostAndPortSet=new HashSet<HostAndPort>();
63 //4、遍历刚刚得到的节点数组
64 for (String node : nodeArray) {
65 //5、继续使用split将node拆分成IP和端口号(host、port)
66 String[] hostAndPortArray = node.split(":");
67 //6、创建HostAndPort对象,并将得到的IP和端口号作为构造方法的参数传入
68 HostAndPort hostAndPort=new HostAndPort(hostAndPortArray[0],Integer.parseInt(hostAndPortArray[1]));
69 //7、把每一个HostAndPort对象装进Set集合中
70 hostAndPortSet.add(hostAndPort);
71 }
72 //8、返回一个JedisCluster对象
73 return new JedisCluster(hostAndPortSet,Integer.parseInt(redisProperties.getCommandTimeout()),Integer.parseInt(redisProperties.getMaxAttempts()));
74 }
75 }
6、在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 * 1、在此处的参数key是一个可变参
57 * 2、在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 * 1、从Redis中查询所有图书信息
19 * 2、判断从Redis中是否查询出数据
20 * 3、没有查询到的话再从数据库中查询数据
21 * 4、判断是否从数据库中查询到数据
22 * 5、若查询到数据,则返回结果,并且将数据放入Redis中
23 * 6、利用try...catch判断是否存入Redis中
24 * 7、若没有存入Redis中,要做出处理,此处为再次往Redis中存放一次
25 */
26 Map<String,Object> resultMap=new HashMap<String, Object>();
27 //1、
28 String bookString=redisService.get(bookKey);
29 //2、
30 if(null==bookString||"".equals(bookString)){
31 //3、
32 List<Book> bookList = bookMapper.selectAll();
33 //4、
34 if(bookList.size()>0){
35 //6、
36 try {
37 //5、
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 //7、
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 }
7、控制层的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 //1、result到最后也没存进去值,数据库都出现了问题
26 System.out.println("数据库出现了问题");
27 }else{
28 //2、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>
来源:oschina
链接:https://my.oschina.net/u/4309066/blog/3411478