這里的緩存主要是用于 service 層的,所以下面的配置,都是針對 service 模塊的。
本文來自內部分享,對特殊信息進行了簡單處理。
本文都是在以緩存來講 redis 的使用,實際上 redis 不僅僅用于緩存,本身還是 nosql 數(shù)據(jù)庫,大家可以自己查找學習 redis 的常用場景。
一、添加依賴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!--緩存--> <dependency> <groupid>org.springframework</groupid> <artifactid>spring-context-support</artifactid> <version> 4.3 . 14 .release</version> </dependency> <!--redis--> <dependency> <groupid>org.springframework.data</groupid> <artifactid>spring-data-redis</artifactid> <version> 1.8 . 10 .release</version> </dependency> <dependency> <groupid>org.apache.commons</groupid> <artifactid>commons-pool2</artifactid> <version> 2.4 . 3 </version> </dependency> <dependency> <groupid>redis.clients</groupid> <artifactid>jedis</artifactid> <version> 2.9 . 0 </version> </dependency> |
二、配置
增加spring-redis.xml
配置文件,內容如下:
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
|
<?xml version= "1.0" encoding= "utf-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/xmlschema-instance" xmlns:cache= "http://www.springframework.org/schema/cache" xmlns:util= "http://www.springframework.org/schema/util" xsi:schemalocation="http: //www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans-2.5.xsd http: //www.springframework.org/schema/cache http: //www.springframework.org/schema/cache/spring-cache.xsd http: //www.springframework.org/schema/util http: //www.springframework.org/schema/util/spring-util.xsd"> <bean id= "jedispoolconfig" class = "redis.clients.jedis.jedispoolconfig" > <property name= "maxidle" value= "${redis.pool.maxidle}" /> <property name= "maxtotal" value= "${redis.pool.maxtotal}" /> <property name= "maxwaitmillis" value= "${redis.pool.maxwaitmillis}" /> <property name= "testonborrow" value= "${redis.pool.testonborrow}" /> <property name= "testonreturn" value= "${redis.pool.testonreturn}" /> </bean> <bean id= "jedisconnectionfactory" class = "org.springframework.data.redis.connection.jedis.jedisconnectionfactory" > <property name= "hostname" value= "${redis.master.ip}" /> <property name= "port" value= "${redis.master.port}" /> <property name= "poolconfig" ref= "jedispoolconfig" /> </bean> <bean id= "rediskeyserializer" class = "org.springframework.data.redis.serializer.stringredisserializer" /> <bean id= "redisvalueserializer" class = "org.springframework.data.redis.serializer.jdkserializationredisserializer" /> <bean id= "redistemplate" class = "org.springframework.data.redis.core.redistemplate" > <property name= "connectionfactory" ref= "jedisconnectionfactory" /> <property name= "keyserializer" ref= "rediskeyserializer" /> <property name= "hashkeyserializer" ref= "rediskeyserializer" /> <property name= "valueserializer" ref= "redisvalueserializer" /> <property name= "hashvalueserializer" ref= "redisvalueserializer" /> </bean> <!--在 redis.properties 配置緩存詳細信息--> <util:properties id= "redisexpires" location= "classpath*:meta-inf/spring/redis.properties" /> <bean id= "cachemanager" class = "org.springframework.data.redis.cache.rediscachemanager" > <constructor-arg index= "0" ref= "redistemplate" /> <!--默認緩存 10 分鐘--> <property name= "defaultexpiration" value= "600" /> <property name= "useprefix" value= "true" /> <property name= "expires" ref= "redisexpires" /> </bean> <!--啟用 cache 注解--> <cache:annotation-driven cache-manager= "cachemanager" proxy-target- class = "true" /> </beans> |
在 src/main/resources/ 下面如果沒有meta-inf/spring/
目錄就創(chuàng)建一個,然后增加 redis.properties 配置,示例如下:
1
2
3
4
5
6
|
# 緩存名=有效時間 halfhour= 1800 onehour= 3600 oneday= 86400 websession= 1800 user= 1800 |
除了上面配置外,在系統(tǒng)的 application.properties 中還需要提供下面幾個配置:
1
2
3
4
5
6
7
8
9
|
# redis 連接配置 redis.master.ip= 10.10 . 10.100 redis.master.port= 6379 # redis 連接池配置 redis.pool.maxidle= 200 redis.pool.maxtotal= 1024 redis.pool.maxwaitmillis= 1000 redis.pool.testonborrow= true redis.pool.testonreturn= true |
三、通過注解方式使用緩存
示例中,redis.propreties 配置如下:
1
2
3
4
|
# 數(shù)據(jù)庫定義,緩存 30 天 databasedef= 2592000 # 數(shù)據(jù)庫元數(shù)據(jù),緩存 1 小時 databasemeta= 3600 |
這個示例在數(shù)據(jù)庫服務上配置的,數(shù)據(jù)庫服務中,查詢次數(shù)遠遠大于新增、修改、刪除的次數(shù),非常適合使用緩存。
1. 緩存數(shù)據(jù) @cacheable
1
2
3
4
5
|
@override @cacheable (value = "databasedef" , key = "'all'" ) public list<databasedefinitionvo> selectall() { return databasedefinitiondao.selectallvo(); } |
特別注意:所有這些注解中,key 的值是 spel 表達式,必須按照 spel 要求來寫。上面這個例子中,直接定義返回值的 key 是 all 字符串,需要加上單引號' 括起來,下面還有其他用法。
在例子中,下面的方法也使用了這個注解:
1
2
3
4
5
6
7
8
|
@override @cacheable (value = "databasedef" , key = "#id.tostring()" ) public databasedefinition selectbyprimarykey( long id) { assert .notnull(id, "數(shù)據(jù)庫 id 不能為空!" ); databasedefinition definition = databasedefinitiondao.selectbyprimarykey(id); assert .notnull(definition, "數(shù)據(jù)庫定義不存在!" ); return definition; } |
在上面注解中,key
中的 #id
指的是參數(shù)中的 id,在 idea 中會有自動提示。.tostring()
是調用 id
的方法,在系統(tǒng)中規(guī)定了 key 必須是字符串類型,所以當類型是 long 的時候,需要轉換。
使用緩存的目的就是為了減少上面兩個方法調用時減少和數(shù)據(jù)庫的交互,減小數(shù)據(jù)庫的壓力,這是兩個主要的緩存數(shù)據(jù)的方法,下面的幾個操作都和上面這兩個方法有一定的關系。
重點:這里以及下面幾個注解中,都指定了
value = "databasedef"
,這里的意思是說,要使用前面配置中的 databasedef 對應的配置,也就是會緩存 30天。
2. 更新緩存 @cacheput
1
2
3
4
5
6
7
|
@override @cacheput (value = "databasedef" , key = "#result.id.tostring()" ) public databasedefinition save(databasedefinition definition, currentuser usermodel) throws serviceexception { //代碼 return definition; } |
更新緩存的方法需要注意的是返回值,在上面 save 方法中,有可能是新增,有可能是更新,不管是那個操作,當操作完成后,上面注解會根據(jù) key 的值生成 key,然后將方法的返回值作為 value 保存到緩存中。
這里 key 的寫法中 #result 指代返回值,.id 是返回值的屬性,.tostring() 是調用 id 屬性的方法,在系統(tǒng)中規(guī)定了 key 必須是字符串類型,所以當類型是 long 的時候,需要轉換。
這個方法上加的緩存還有問題,當新增或者更新后,通過 selectall() 返回的值已經(jīng)發(fā)生了變化,但是這里沒有清除 all 的緩存值,會導致 selectall() 出現(xiàn)臟數(shù)據(jù),下面會通過 @caching 注解改造這里。
3. 清除緩存 @cacheevict
1
2
3
4
5
6
7
8
9
|
@override @cacheevict (value = "databasedef" , key = "#id.tostring()" ) public void deletebyprimarykey( long id) throws serviceexception { databasedefinition definition = selectbyprimarykey(id); if (definition.getloadstate().equals(databasedefinition.loadstate.up)) { throw new serviceexception( "請先卸載數(shù)據(jù)庫!" ); } databasedefinitiondao.deletebyprimarykey(id); } |
在上面新增或者修改的時候根據(jù) id 緩存或者更新了緩存數(shù)據(jù),這里當刪除數(shù)據(jù)的時候,還需要清空對應的緩存數(shù)據(jù)。
在上面注解中,key 中的 #id 指的是參數(shù)中的 id,在 idea 中會有自動提示。
這個方法上加的緩存還有問題,當刪除后,通過 selectall() 返回的值已經(jīng)發(fā)生了變化,但是這里沒有清除 all 的緩存值,會導致 selectall() 出現(xiàn)臟數(shù)據(jù),下面會通過 @caching 注解改造這里。
4. 組合使用 @caching
上面兩個注解中,都提到了臟數(shù)據(jù),通過 @caching 注解可以解決這個問題。
先修改第二個注解,來解決 save 時的臟數(shù)據(jù):
1
2
3
4
5
6
7
8
|
@override @caching (put = @cacheput (value = "databasedef" , key = "#result.id.tostring()" ), evict = @cacheevict (value = "databasedef" , key = "'all'" )) public databasedefinition save(databasedefinition definition, currentuser usermodel) throws serviceexception { //其他代碼 return definition; } |
前面說明,新增或者修改的時候,all 緩存中的數(shù)據(jù)已經(jīng)不對了,因此這里在 put 的同時,使用 evict 將 'all' 中的數(shù)據(jù)清除,這就保證了 selelctall 下次調用時,會重新從庫中讀取數(shù)據(jù)。
對上面的刪除方法,也進行類似的修改:
1
2
3
4
5
6
7
8
9
10
11
12
|
@override @caching (evict = { @cacheevict (value = "databasedef" , key = "#id.tostring()" ), @cacheevict (value = "databasedef" , key = "'all'" ) }) public void deletebyprimarykey( long id) throws serviceexception { databasedefinition definition = selectbyprimarykey(id); if (definition.getloadstate().equals(databasedefinition.loadstate.up)) { throw new serviceexception( "請先卸載數(shù)據(jù)庫!" ); } databasedefinitiondao.deletebyprimarykey(id); } |
注意這里的 evict 是個數(shù)組,里面配置了兩個清除緩存的配置。
5. 全局配置 @cacheconfig
在上面所有例子中,都指定了 value = "databasedef",實際上可以通過在類上使用 @cacheconfig 注解配置當前類中的 cachenames 值,配置后,如果和類上的 value 一樣就不需要在每個注解單獨配置。只有不同時再去指定,方法上的 value 值優(yōu)先級更高。
1
2
3
4
|
@service @cacheconfig (cachenames = "databasedef" ) public class databasedefinitionserviceimpl implements databasedefinitionservice, databasesqlexecuteservice, applicationlistener { |
有了上面配置后,其他緩存名字相同的地方可以簡化,例如刪除方法修改后如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
@override @caching (evict = { @cacheevict (key = "#id.tostring()" ), @cacheevict (key = "'all'" ) }) public void deletebyprimarykey( long id) throws serviceexception { databasedefinition definition = selectbyprimarykey(id); if (definition.getloadstate().equals(databasedefinition.loadstate.up)) { throw new serviceexception( "請先卸載數(shù)據(jù)庫!" ); } databasedefinitiondao.deletebyprimarykey(id); } |
其他例子
除了上面針對 databasedef 的緩存外,還有 databasemeta 的配置:
1
2
3
4
5
6
7
8
9
10
11
12
|
@override @cacheable (value = "databasemeta" , key = "#databaseid.tostring()" ) public list<tablevo> selecttablesbydatabaseid( long databaseid) throws exception { //代碼 } @override @cacheable (value = "databasemeta" , key = "#databaseid + '_' + #tablename" ) public tablevo selecttablebydatabaseidandtablename( long databaseid, string tablename) throws exception { //代碼 } |
這兩個方法是獲取數(shù)據(jù)庫元數(shù)據(jù)的,只有修改數(shù)據(jù)庫表的時候才會變化,因此不存在清除緩存的情況,但是萬一修改表后,想要新的元數(shù)據(jù),該怎么辦?
因此增加了一個空的方法來清空數(shù)據(jù),方法如下:
1
2
3
4
|
@override @cacheevict (value = "databasemeta" , allentries = true ) public void cleantablescache() { } |
這里指定了 databasemeta,通過 allentries = true 清空所有 key 的緩存。通過這個方法可以保證在有需要的時候清空所有元數(shù)據(jù)的緩存。
實際上如果想要更精確的清除,可以傳入要清除的 databaseid 和 tablename 來更精確的清除。
增加緩存后的效果
調用 selecttablesbydatabaseid 多次時,輸出的日志如下:
info c.n.d.u.dynamicdatasource - 當前數(shù)據(jù)源:默認數(shù)據(jù)源
debug c.n.d.datascopecontextproviderfilter - 服務調用返回前清除數(shù)據(jù)權限信息
info c.n.d.u.dynamicdatasource - 當前數(shù)據(jù)源:默認數(shù)據(jù)源
debug c.n.d.d.d.selectbyprimarykey - ==> preparing: select xxx (隱藏完整 sql)
debug c.n.d.d.d.selectbyprimarykey - ==> parameters: 1(long)
debug c.n.d.d.d.selectbyprimarykey - <== total: 1
info c.n.d.u.dynamicdatasource - 當前數(shù)據(jù)源:10.10.10.130/datareporting
debug c.n.d.datascopecontextproviderfilter - 服務調用返回前清除數(shù)據(jù)權限信息
info c.n.d.u.dynamicdatasource - 當前數(shù)據(jù)源:默認數(shù)據(jù)源
debug c.n.d.datascopecontextproviderfilter - 服務調用返回前清除數(shù)據(jù)權限信息
info c.n.d.u.dynamicdatasource - 當前數(shù)據(jù)源:默認數(shù)據(jù)源
debug c.n.d.datascopecontextproviderfilter - 服務調用返回前清除數(shù)據(jù)權限信息
從日志可以看出來,只有第一次進行了數(shù)據(jù)庫查詢,后續(xù)通過日志看不到數(shù)據(jù)庫操作。
調用清空緩存后,會再次查詢數(shù)據(jù)庫。
初次調用時,web請求花了 700多ms,后面再次調用時,平均不到 30 ms,這就是緩存最明顯的作用。
連接到 redis 服務后,查看所有 key,結果如下:
redis@redissvr:~$ redis-cli
127.0.0.1:6379> keys *
1) "databasemeta:1"
2) "databasedef:all"
127.0.0.1:6379>
緩存中的數(shù)據(jù)都有 value 前綴,上面緩存了 all 和 id 為 1 的數(shù)據(jù)。
緩存注解是一種最簡單的緩存方式,但是需要配合 value 屬性的配置來使用,許多時候我們可能需要更精確的控制緩存,此時可以使用 redistemplate 來控制。
四、通過 redistemplate 使用緩存
有關這部分的詳細用法可以從網(wǎng)上搜索相關內容進行學習,這里列舉一個簡單的例子。
針對前面的 selectall 我們換一種方式進行緩存。
首先注入下面的接口:
1
2
|
@resource (name = "redistemplate" ) private valueoperations<string, list> valueoper; |
修改 selectall 方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
@override //@cacheable(key = "'all'") public list<databasedefinitionvo> selectall() { list<databasedefinitionvo> vos = valueoper.get( "databasedef:all" ); if (vos != null ){ return vos; } vos = databasedefinitiondao.selectallvo(); //緩存 1 小時 valueoper.set( "databasedef:all" , vos, 1 , timeunit.hours); return vos; } |
首先通過valueoper.get("databasedef:all")
嘗試獲取緩存信息,如果存在就直接返回。
如果不存在,就查詢數(shù)據(jù)庫,然后將查詢結果通過 set 進行緩存。
特別注意: 上面的 key,寫的是 "databasedef:all",也就是前綴需要自己加上,如果直接寫成 all,在 redis 中的 key 就是 all,不會自動增加前綴。
如果沒有前綴,那么當不同系統(tǒng)都使用 all 時,數(shù)據(jù)就會混亂!
五、redis 服務器配置注意事項
內網(wǎng)使用的服務器,特殊配置如下:
1
2
3
4
5
6
|
# by default protected mode is enabled. you should disable it only if # you are sure you want clients from other hosts to connect to redis # even if no authentication is configured, nor a specific set of interfaces # are explicitly listed using the "bind" directive. # protected -mode yes protected -mode no |
關閉了保護模式。
1
2
3
4
|
# if you are sure you want your instance to listen to all the interfaces # just comment the following line. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # bind 127.0 . 0.1 |
注釋了綁定的 ip,這樣可以讓所有電腦訪問 redis。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對服務器之家的支持。如果你想了解更多相關內容請查看下面相關鏈接
原文鏈接:https://blog.csdn.net/isea533/article/details/84563949