pom.xml
增加对 Redis 支持的包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
<?
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 取到的数据,还是依然是从数据库取到的数据
同时让hibernate的sql语句显示出来,这样才知道到底是通过 Redis 取到的数据,还是依然是从数据库取到的数据
spring.jpa.show-sql=true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 工具也无法识别。
这个配置,一个作用: 让保存到 Redis 里的 key 和 value 都转换为可读的 json 格式。 否则会是二进制格式,通过 RedisClient 工具也无法识别。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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;
}
}
|
步骤 11 :
Page4Navigator
创建一个工具类 Page4Navigator 用以替换 原本分页查询要返回的 org.springframework.data.domain.Page 类。 原因是 Page 类对json 还原不支持,在放如 Redis 之后,再拿出来,就会报错失败。
使用 Page4Navigator 对其包裹,就解决了这个问题了。
使用 Page4Navigator 对其包裹,就解决了这个问题了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
|
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 类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 这个分组里。
4. list方法讲解:
先说注解
假如是第一页,即offset=0,pageSize=0,那么会创建一个 key: "category 0-5"
首先根据这个key 到 redis中查询数据。 第一次是不会有数据的,那么就会从数据库中取到这5条数据,然后以这个 key: "category 0-5" 保存到 redis 数据库中。
下一次再次访问的时候,根据这个key,就可以从 redis 里取到数据了。
5. get 方法讲解
先说注解:
假如是获取id=71的数据,那么
就会以 key= "category 71" 到reids中去获取,如果没有就会从数据库中拿到,然后再以 key= "category 71" 这个值存放到 redis 当中。
下一次再次访问的时候,根据这个key,就可以从 redis 里取到数据了。
6. add 方法讲解
先说注解:
可以看到,本来有个 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 方法讲解
先说注解:
这个道理和 add 是一样的,仅仅删除 key= "category 71" ,没有什么意义, key: "category 0-5" 里面的数据没有影响呀。 所以还是通过 CacheEvict删除掉所有的缓存就好了。
1. 实现 CategoryService 接口,提供 crud
2. 在相应方法实现的时候,都是通过调用 dao 实现的
3. CacheConfig,表示 分类数据在 redis 中都放在 category 这个分组里。
@CacheConfig(cacheNames="category")
4. list方法讲解:
先说注解
@Cacheable(key="'category '+#p0.offset + '-' + #p0.pageSize ")
假如是第一页,即offset=0,pageSize=0,那么会创建一个 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删除掉所有的缓存就好了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
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 获取了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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"
;
}
}
|