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"
;
}
}
|