A. Spring如何配置資料庫查詢緩存/對象緩存EHCache
在ehcache.xml文件中配置查詢緩存參數,ehcache.xml文件配置如下:
<ehcache>
<!--diskStore元素,配置一個目錄,這個目錄用來存放數據,
也就是說,如果EhCache需要把數據寫入磁碟,將會寫到這個目錄下-->
<diskStorepath="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskPersistent="false"
="120"/>
<cachename="ehcacheName"
maxElementsInMemory="3000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="36000"
overflowToDisk="true"
/>
</ehcache>
2. spring的配置
第一步:給指定方法配置緩存/src/main/resources/applicationContext-resources.xml
<ehcache:proxyid="userGroupServiceProxy"refId="userGroupService">
<ehcache:cachingcacheName="cash15Min"methodName=""/>
<ehcache:cachingcacheName="cash15Min"methodName=""/>
<ehcache:cachingcacheName="cash15Min"methodName="selectuserGroupById"/>
</ehcache:proxy>
配置參數的含義如下:
id:唯一標識符
refId:需要配置緩存的service或者controller
cacheName:緩存名稱
methodName:需要緩存的方法,這個方法必須是shoppingHomeService中的方法
第二步:在控制器中注入依賴的緩存userGroupServiceProxy /src/main/webapp/WEB-INF/dispatcher-servlet.xml
<beanid="PFController"class="com.java.mall.controller.PFController">
<propertyname="userService"ref="userService"></property>
<propertyname="userGroupService"ref="userGroupServiceProxy"></property>
</bean>
同時需要在實體類中注入依賴,提供setter方法,
;
publicvoidsetuserGroupService(){
this.userGroupService=userGroupService;
}
B. spring cache 使用什麼框架
從3.1開始,Spring引入了對Cache的支持。其使用方法和原理都類似於Spring對事務管理的支持。Spring Cache是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和返回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用Spring Cache的時候我們要保證我們緩存的方法對於相同的方法參數要有相同的返回結果。
使用Spring Cache需要我們做兩方面的事:
聲明某些方法使用緩存
配置Spring對Cache的支持
和Spring對事務管理的支持一樣,Spring對Cache的支持也有基於註解和基於XML配置兩種方式。下面我們先來看看基於註解的方式。
1基於註解的支持
Spring為我們提供了幾個註解來支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable標記的方法在執行後Spring Cache將緩存其返回結果,而使用@CacheEvict標記的方法會在方法執行前或者執行後移除Spring Cache中的某些元素。下面我們將來詳細介紹一下Spring基於註解對Cache的支持所提供的幾個註解。
1.1@Cacheable
@Cacheable可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類所有的方法都是支持緩存的。對於一個支持緩存的方法,Spring會在其被調用後將其返回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中獲取結果,而不需要再次執行該方法。Spring在緩存方法的返回值時是以鍵值對進行緩存的,值就是方法的返回結果,至於鍵的話,Spring又支持兩種策略,默認策略和自定義策略,這個稍後會進行說明。需要注意的是當一個支持緩存的方法在對象內部被調用時是不會觸發緩存功能的。@Cacheable可以指定三個屬性,value、key和condition。
1.1.1value屬性指定Cache名稱
value屬性是必須指定的,其表示當前方法的返回值是會被緩存在哪個Cache上的,對應Cache的名稱。其可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個數組。
@Cacheable("cache1")//Cache是發生在cache1上的
publicUserfind(Integerid){
returnnull;
}
@Cacheable({"cache1","cache2"})//Cache是發生在cache1和cache2上的
publicUserfind(Integerid){
returnnull;
}
1.1.2使用key屬性自定義key
key屬性是用來指定Spring緩存方法的返回結果時對應的key的。該屬性支持SpringEL表達式。當我們沒有指定該屬性時,Spring將使用默認策略生成key。我們這里先來看看自定義策略,至於默認策略會在後文單獨介紹。
自定義策略是指我們可以通過Spring的EL表達式來指定我們的key。這里的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用「#參數名」或者「#p參數index」。下面是幾個使用參數作為key的示例。
@Cacheable(value="users",key="#id")
publicUserfind(Integerid){
returnnull;
}
@Cacheable(value="users",key="#p0")
publicUserfind(Integerid){
returnnull;
}
@Cacheable(value="users",key="#user.id")
publicUserfind(Useruser){
returnnull;
}
@Cacheable(value="users",key="#p0.id")
publicUserfind(Useruser){
returnnull;
}
除了上述使用方法參數作為key之外,Spring還為我們提供了一個root對象可以用來生成key。通過該root對象我們可以獲取到以下信息。
<!--Ehcache實現-->
<beanid="cacheManager"class="org.springframework.cache.ehcache.EhCacheCacheManager"p:cache-manager-ref="ehcacheManager"/>
<beanid="ehcacheManager"class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"p:config-location="ehcache-spring.xml"/>
上面的配置使用了一個Spring提供的EhCacheCacheManager來生成一個Spring的CacheManager,其接收一個Ehcache的CacheManager,因為真正用來存入緩存數據的還是Ehcache。Ehcache的CacheManager是通過Spring提供的EhCacheManagerFactoryBean來生成的,其可以通過指定ehcache的配置文件位置來生成一個Ehcache的CacheManager。若未指定則將按照Ehcache的默認規則取classpath根路徑下的ehcache.xml文件,若該文件也不存在,則獲取Ehcache對應jar包中的ehcache-failsafe.xml文件作為配置文件。更多關於Ehcache的內容這里就不多說了,它不屬於本文討論的內容,欲了解更多關於Ehcache的內容可以參考我之前發布的Ehcache系列文章,也可以參考官方文檔等。
C. 關於spring boot 緩存application.yml文件的配置
這些方式優先順序如下:命令行參數來自java:comp/env的JNDI屬性Java系統屬性(System.getProperties())操作系統環境變數RandomValuePropertySource配置的random.*屬性值jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置文件jar包內部的application-{profile}.properties或application.yml(帶spring.profile)配置文件jar包外部的application.properties或application.yml(不帶spring.profile)配置文件jar包內部的application.properties或application.yml(不帶spring.profile)配置文件@Configuration註解類上的@PropertySource
D. 如何在spring中使用緩存
在Spring緩存機制中,包括了兩個方面的緩存操作:1.緩存某個方法返回的結果;2.在某個方法執行前或後清空緩存。
Spring僅僅是提供了對緩存的支持,但它並沒有任何的緩存功能的實現,spring使用的是第三方的緩存框架來實現緩存的功能。其中,spring對EHCache提供了很好的支持。
參考博客:http://www.cnblogs.com/fysola/p/6378400.html
E. 圖文並茂,揭秘 Spring 的 Bean 的載入過程
目錄
Spring 作為 Ioc 框架,實現了依賴注入,由一個中心化的 Bean 工廠來負責各個 Bean 的實例化和依賴管理。各個 Bean 可以不需要關心各自的復雜的創建過程,達到了很好的解耦效果。
我們對 Spring 的工作流進行一個粗略的概括,主要為兩大環節:
我們假設所有的配置和擴展類都已經裝載到了 ApplicationContext 中,然後具體的分析一下 Bean 的載入流程。
思考一個問題,拋開 Spring 框架的實現,假設我們手頭上已經有一套完整的 Bean Definition Map,然後指定一個 beanName 要進行實例化,需要關心什麼?即使我們沒有 Spring 框架,也需要了解這兩方面的知識:
Spring 進行了抽象和封裝,使得作用域和依賴關系的配置對開發者透明,我們只需要知道當初在配置里已經明確指定了它的生命周期和依賴了誰,至於是怎麼實現的,依賴如何注入,託付給了 Spring 工廠來管理。
Spring 只暴露了很簡單的介面給調用者,比如 getBean :
那我們就從 getBean 方法作為入口,去理解 Spring 載入的流程是怎樣的,以及內部對創建信息、作用域、依賴關系等等的處理細節。
上面是跟蹤了 getBean 的調用鏈創建的流程圖,為了能夠很好地理解 Bean 載入流程,省略一些異常、日誌和分支處理和一些特殊條件的判斷。
從上面的流程圖中,可以看到一個 Bean 載入會經歷這么幾個階段(用綠色標記):
整個流程最為復雜的是對循環依賴的解決方案,後續會進行重點分析。
而在我們解析完配置後創建的 Map,使用的是 beanName 作為 key。見 DefaultListableBeanFactory:
BeanFactory.getBean 中傳入的 name,有可能是這幾種情況:
為了能夠獲取到正確的 BeanDefinition,需要先對 name 做一個轉換,得到 beanName。
見 AbstractBeanFactory.doGetBean :
如果是 alias name ,在解析階段,alias name 和 bean name 的映射關系被注冊到 SimpleAliasRegistry 中。從該注冊器中取到 beanName。見 SimpleAliasRegistry.canonicalName :
如果是 factorybean name ,表示這是個工廠 bean,有攜帶前綴修飾符 & 的,直接把前綴去掉。見 BeanFactoryUtils.transformedBeanName :
我們從配置文件讀取到的 BeanDefinition 是 GenericBeanDefinition 。它的記錄了一些當前類聲明的屬性或構造參數,但是對於父類只用了一個 parentName 來記錄。
接下來會發現一個問題,在後續實例化 Bean 的時候,使用的 BeanDefinition 是 RootBeanDefinition 類型而非 GenericBeanDefinition 。這是為什麼?
答案很明顯,GenericBeanDefinition 在有繼承關系的情況下,定義的信息不足:
為了能夠正確初始化對象,需要完整的信息才行 。需要遞歸 合並父類的定義 :
見 AbstractBeanFactory.doGetBean :
在判斷 parentName 存在的情況下,說明存在父類定義,啟動合並。如果父類還有父類怎麼辦?遞歸調用,繼續合並。
見 AbstractBeanFactory.getMergedBeanDefinition 方法:
每次合並完父類定義後,都會調用 RootBeanDefinition.overrideFrom 對父類的定義進行覆蓋,獲取到當前類能夠正確實例化的 全量信息 。
什麼是循環依賴?
舉個例子,這里有三個類 A、B、C,然後 A 關聯 B,B 關聯 C,C 又關聯 A,這就形成了一個循環依賴。如果是方法調用是不算循環依賴的,循環依賴必須要持有引用。
循環依賴根據注入的時機分成兩種類型:
如果是構造器循環依賴,本質上是無法解決的 。比如我們准調用 A 的構造器,發現依賴 B,於是去調用 B 的構造器進行實例化,發現又依賴 C,於是調用 C 的構造器去初始化,結果依賴 A,整個形成一個死結,導致 A 無法創建。
如果是設值循環依賴,Spring 框架只支持單例下的設值循環依賴 。Spring 通過對還在創建過程中的單例,緩存並提前暴露該單例,使得其他實例可以引用該依賴。
Spring 不支持原型模式的任何循環依賴 。檢測到循環依賴會直接拋出 異常。
使用了一個 ThreadLocal 變數 prototypesCurrentlyInCreation 來記錄當前線程正在創建中的 Bean 對象,見 AbtractBeanFactory#prototypesCurrentlyInCreation :
在 Bean 創建前進行記錄,在 Bean 創建後刪除記錄。見 AbstractBeanFactory.doGetBean :
見 AbtractBeanFactory.beforePrototypeCreation 的記錄操作:
見 AbtractBeanFactory.beforePrototypeCreation 的刪除操作:
為了節省內存空間,在單個元素時 prototypesCurrentlyInCreation 只記錄 String 對象,在多個依賴元素後改用 Set 集合。這里是 Spring 使用的一個節約內存的小技巧。
了解了記錄的寫入和刪除過程好了,再來看看讀取以及判斷循環的方式。這里要分兩種情況討論。
這兩個地方的實現略有不同。
如果是構造函數依賴的,比如 A 的構造函數依賴了 B,會有這樣的情況。實例化 A 的階段中,匹配到要使用的構造函數,發現構造函數有參數 B,會使用 BeanDefinitionValueResolver 來檢索 B 的實例。見 BeanDefinitionValueResolver.resolveReference :
我們發現這里繼續調用 beanFactory.getBean 去載入 B。
如果是設值循環依賴的的,比如我們這里不提供構造函數,並且使用了 @Autowire 的方式註解依賴(還有其他方式不舉例了):
載入過程中,找到無參數構造函數,不需要檢索構造參數的引用,實例化成功。接著執行下去,進入到屬性填充階段 AbtractBeanFactory.populateBean ,在這里會進行 B 的依賴注入。
為了能夠獲取到 B 的實例化後的引用,最終會通過檢索類 DependencyDescriptor 中去把依賴讀取出來,見 DependencyDescriptor.resolveCandidate :
發現 beanFactory.getBean 方法又被調用到了。
在這里,兩種循環依賴達成了同一 。無論是構造函數的循環依賴還是設置循環依賴,在需要注入依賴的對象時,會繼續調用 beanFactory.getBean 去載入對象,形成一個遞歸操作。
而每次調用 beanFactory.getBean 進行實例化前後,都使用了 prototypesCurrentlyInCreation 這個變數做記錄。按照這里的思路走,整體效果等同於 建立依賴對象的構造鏈 。
prototypesCurrentlyInCreation 中的值的變化如下:
調用判定的地方在 AbstractBeanFactory.doGetBean 中,所有對象的實例化均會從這里啟動。
判定的實現方法為 AbstractBeanFactory. :
所以在原型模式下,構造函數循環依賴和設值循環依賴,本質上使用同一種方式檢測出來。Spring 無法解決,直接拋出 異常。
Spring 也不支持單例模式的構造循環依賴 。檢測到構造循環依賴也會拋出 異常。
和原型模式相似,單例模式也用了一個數據結構來記錄正在創建中的 beanName。見 DefaultSingletonBeanRegistry :
會在創建前進行記錄,創建化後刪除記錄。
見 DefaultSingletonBeanRegistry.getSingleton
記錄和判定的方式見 DefaultSingletonBeanRegistry.beforeSingletonCreation :
這里會嘗試往 singletonsCurrentlyInCreation 記錄當前實例化的 bean。我們知道 singletonsCurrentlyInCreation 的數據結構是 Set,是不允許重復元素的, 所以一旦前面記錄了,這里的 add 操作將會返回失敗 。
比如載入 A 的單例,和原型模式類似,單例模式也會調用匹配到要使用的構造函數,發現構造函數有參數 B,然後使用 BeanDefinitionValueResolver 來檢索 B 的實例,根據上面的分析,繼續調用 beanFactory.getBean 方法。
所以拿 A,B,C 的例子來舉例 singletonsCurrentlyInCreation 的變化,這里可以看到和原型模式的循環依賴判斷方式的演算法是一樣:
單例模式下,構造函數的循環依賴無法解決,但設值循環依賴是可以解決的 。
這里有一個重要的設計: 提前暴露創建中的單例 。
我們理解一下為什麼要這么做。
還是拿上面的 A、B、C 的的設值依賴做分析,
=> 1. A 創建 -> A 構造完成,開始注入屬性,發現依賴 B,啟動 B 的實例化
=> 2. B 創建 -> B 構造完成,開始注入屬性,發現依賴 C,啟動 C 的實例化
=> 3. C 創建 -> C 構造完成,開始注入屬性,發現依賴 A
重點來了,在我們的階段 1中, A 已經構造完成,Bean 對象在堆中也分配好內存了,即使後續往 A 中填充屬性(比如填充依賴的 B 對象),也不會修改到 A 的引用地址。
所以,這個時候是否可以 提前拿 A 實例的引用來先注入到 C ,去完成 C 的實例化,於是流程變成這樣。
=> 3. C 創建 -> C 構造完成,開始注入依賴,發現依賴 A,發現 A 已經構造完成,直接引用,完成 C 的實例化。
=> 4. C 完成實例化後,B 注入 C 也完成實例化,A 注入 B 也完成實例化。
這就是 Spring 解決單例模式設值循環依賴應用的技巧。流程圖為:
為了能夠實現單例的提前暴露。Spring 使用了三級緩存,見 DefaultSingletonBeanRegistry :
這三個緩存的區別如下:
從 getBean("a") 開始,添加的 SingletonFactory 具體實現如下:
可以看到如果使用該 SingletonFactory 獲取實例,使用的是 getEarlyBeanReference 方法,返回一個未初始化的引用。
讀取緩存的地方見 DefaultSingletonBeanRegistry :
先嘗試從 singletonObjects 和 singletonFactory 讀取,沒有數據,然後嘗試 singletonFactories 讀取 singletonFactory,執行 getEarlyBeanReference 獲取到引用後,存儲到 earlySingletonObjects 中。
這個 earlySingletonObjects 的好處是,如果此時又有其他地方嘗試獲取未初始化的單例,可以從 earlySingletonObjects 直接取出而不需要再調用 getEarlyBeanReference 。
從流程圖上看,實際上注入 C 的 A 實例,還在填充屬性階段,並沒有完全地初始化。等遞歸回溯回去,A 順利拿到依賴 B,才會真實地完成 A 的載入。
獲取到完整的 RootBeanDefintion 後,就可以拿這份定義信息來實例具體的 Bean。
具體實例創建見 .createBeanInstance ,返回 Bean 的包裝類 BeanWrapper,一共有三種策略:
使用工廠方法創建,會先使用 getBean 獲取工廠類,然後通過參數找到匹配的工廠方法,調用實例化方法實現實例化,具體見 ConstructorResolver.instantiateUsingFactoryMethod :
使用有參構造函數創建,整個過程比較復雜,涉及到參數和構造器的匹配。為了找到匹配的構造器,Spring 花了大量的工作,見 ConstructorResolver.autowireConstructor :
使用無參構造函數創建是最簡單的方式,見 .instantiateBean :
我們發現這三個實例化方式,最後都會走 getInstantiationStrategy().instantiate(...) ,見實現類 SimpleInstantiationStrategy.instantiate :
雖然拿到了構造函數,並沒有立即實例化。因為用戶使用了 replace 和 lookup 的配置方法,用到了動態代理加入對應的邏輯。如果沒有的話,直接使用反射來創建實例。
創建實例後,就可以開始注入屬性和初始化等操作。
但這里的 Bean 還不是最終的 Bean。返回給調用方使用時,如果是 FactoryBean 的話需要使用 getObject 方法來創建實例。見 AbstractBeanFactory.getObjectFromBeanInstance ,會執行到 doGetObjectFromFactoryBean :
實例創建完後開始進行屬性的注入,如果涉及到外部依賴的實例,會自動檢索並關聯到該當前實例。
Ioc 思想體現出來了。正是有了這一步操作,Spring 降低了各個類之間的耦合。
屬性填充的入口方法在 .populateBean 。
可以看到主要的處理環節有:
如果我們的 Bean 需要容器的一些資源該怎麼辦?比如需要獲取到 BeanFactory、ApplicationContext 等等。
Spring 提供了 Aware 系列介面來解決這個問題。比如有這樣的 Aware:
Spring 在初始化階段,如果判斷 Bean 實現了這幾個介面之一,就會往 Bean 中注入它關心的資源。
見 .invokeAwareMethos :
在 Bean 的初始化前或者初始化後,我們如果需要進行一些增強操作怎麼辦?
這些增強操作比如打日誌、做校驗、屬性修改、耗時檢測等等。Spring 框架提供了 BeanPostProcessor 來達成這個目標。比如我們使用註解 @Autowire 來聲明依賴,就是使用 來實現依賴的查詢和注入的。介面定義如下:
實現該介面的 Bean 都會被 Spring 注冊到 beanPostProcessors 中, 見 AbstractBeanFactory :
只要 Bean 實現了 BeanPostProcessor 介面,載入的時候會被 Spring 自動識別這些 Bean,自動注冊,非常方便。
然後在 Bean 實例化前後,Spring 會去調用我們已經注冊的 beanPostProcessors 把處理器都執行一遍。
這里使用了責任鏈模式,Bean 會在處理器鏈中進行傳遞和處理。當我們調用 BeanFactory.getBean 的後,執行到 Bean 的初始化方法 .initializeBean 會啟動這些處理器。
自定義初始化有兩種方式可以選擇:
見 .invokeInitMethods :
Bean 已經載入完畢,屬性也填充好了,初始化也完成了。
在返回給調用者之前,還留有一個機會對 Bean 實例進行類型的轉換。見 AbstractBeanFactory.doGetBean :
拋開一些細節處理和擴展功能,一個 Bean 的創建過程無非是:
獲取完整定義 -> 實例化 -> 依賴注入 -> 初始化 -> 類型轉換。
作為一個完善的框架,Spring 需要考慮到各種可能性,還需要考慮到接入的擴展性。
所以有了復雜的循環依賴的解決,復雜的有參數構造器的匹配過程,有了 BeanPostProcessor 來對實例化或初始化的 Bean 進行擴展修改。
先有個整體設計的思維,再逐步擊破針對這些特殊場景的設計,整個 Bean 載入流程迎刃而解。
F. spring boot@cacheable的緩存怎麼使用
從3.1開始,Spring引入了對Cache的支持。其使用方法和原理都類似於Spring對事務管理的支持。Spring Cache是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和返回結果作為一個鍵值對存放在緩存中
G. spring一級緩存和二級緩存的區別是什麼
一級緩存:
就是Session級別的緩存。一個Session做了一個查詢操作,它會把這個操作的結果放在一級緩存中。
如果短時間內這個session(一定要同一個session)又做了同一個操作,那麼hibernate直接從一級緩存中拿,而不會再去連資料庫,取數據。
它是內置的事務范圍的緩存,不能被卸載。
二級緩存:
就是SessionFactory級別的緩存。顧名思義,就是查詢的時候會把查詢結果緩存到二級緩存中。
如果同一個sessionFactory創建的某個session執行了相同的操作,hibernate就會從二級緩存中拿結果,而不會再去連接資料庫。
這是可選的插件式的緩存,在默認情況下,SessionFactory不會啟用這個插件。
可以在每個類或每個集合的粒度上配置。緩存適配器用於把具體的緩存實現軟體與Hibernate集成。
嚴格意義上說,SessionFactory緩存分為兩類:內置緩存和外置緩存。我們通常意義上說的二級緩存是指外置緩存。
內置緩存與session級別緩存實現方式相似。前者是SessionFactory對象的一些集合屬性包含的數據,後者是指Session的一些集合屬性包含的數據
SessionFactory的內置緩存中存放了映射元數據和預定義SQL語句。
映射元數據是映射文件中數據的拷貝;
而預定義SQL語句是在Hibernate初始化階段根據映射元數據推導出來。
SessionFactory的內置緩存是只讀的,應用程序不能修改緩存中的映射元數據和預定義SQL語句,因此SessionFactory不需要進行內置緩存與映射文件的同步。
Hibernate的這兩級緩存都位於持久化層,存放的都是資料庫數據的拷貝。
緩存的兩個特性:
緩存的范圍
緩存的並發訪問策略
1、緩存的范圍
決定了緩存的生命周期以及可以被誰訪問。緩存的范圍分為三類。
事務范圍
進程范圍
集群范圍
註:
對大多數應用來說,應該慎重地考慮是否需要使用集群范圍的緩存,因為訪問的速度不一定會比直接訪問資料庫數據的速度快多少。
事務范圍的緩存是持久化層的第一級緩存,通常它是必需的;進程范圍或集群范圍的緩存是持久化層的第二級緩存,通常是可選的。
2、緩存的並發訪問策略
當多個並發的事務同時訪問持久化層的緩存的相同數據時,會引起並發問題,必須採用必要的事務隔離措施。
在進程范圍或集群范圍的緩存,即第二級緩存,會出現並發問題。
因此可以設定以下四種類型的並發訪問策略,每一種策略對應一種事務隔離級別。
事務型並發訪問策略是事務隔離級別最高,只讀型的隔離級別最低。事務隔離級別越高,並發性能就越低。
A 事務型:僅僅在受管理環境中適用。它提供了Repeatable Read事務隔離級別。
對於經常被讀但很少修改的數據,可以採用這種隔離類型,因為它可以防止臟讀和不可重復讀這類的並發問題。
B 讀寫型:提供了Read Committed事務隔離級別。僅僅在非集群的環境中適用。
對於經常被讀但很少修改的數據,可以採用這種隔離類型,因為它可以防止臟讀這類的並發問題。
C 非嚴格讀寫型:不保證緩存與資料庫中數據的一致性。
如果存在兩個事務同時訪問緩存中相同數據的可能,必須為該數據配置一個很短的數據過期時間,從而盡量避免臟讀。
對於極少被修改,並且允許偶爾臟讀的數據,可以採用這種並發訪問策略。
D 只讀型:對於從來不會修改的數據,如參考數據,可以使用這種並發訪問策略。
什麼樣的數據適合存放到第二級緩存中?
1、很少被修改的數據
2、不是很重要的數據,允許出現偶爾並發的數據
3、不會被並發訪問的數據
4、參考數據
不適合存放到第二級緩存的數據?
1、經常被修改的數據
2、財務數據,絕對不允許出現並發
3、與其他應用共享的數據。
Hibernate的二級緩存策略的一般過程如下:
1) 條件查詢的時候,總是發出一條select * from table_name where …. (選擇所有欄位)這樣的SQL語句查詢資料庫,一次獲得所有的數據對象。
2) 把獲得的所有數據對象根據ID放入到第二級緩存中。
3) 當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那麼從二級緩存中查;查不到,再查詢資料庫,把結果按照ID放入到緩存。
4) 刪除、更新、增加數據的時候,同時更新緩存。
註:
Hibernate的二級緩存策略,是針對於ID查詢的緩存策略,對於條件查詢則毫無作用。為此,Hibernate提供了針對條件查詢的Query緩存。
Query緩存策略的過程如下:
1) Hibernate首先根據這些信息組成一個Query Key,Query Key包括條件查詢的請求一般信息:SQL, SQL需要的參數,記錄范圍(起始位置rowStart,最大記錄個數maxRows),等。
2) Hibernate根據這個Query Key到Query緩存中查找對應的結果列表。如果存在,那麼返回這個結果列表;如果不存在,查詢資料庫,獲取結果列表,把整個結果列表根據Query Key放入到Query緩存中。
3) Query Key中的SQL涉及到一些表名,如果這些表的任何數據發生修改、刪除、增加等操作,這些相關的Query Key都要從緩存中清空。
H. windows環境下Redis+Spring緩存實例
一、Redis了解
1.1、Redis介紹:
redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字元串)、list(鏈表)、set(集合)、zset(sorted set –有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。區別的是redis會周期性的把更新的數據寫入磁碟或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。
Redis資料庫完全在內存中,使用磁碟僅用於持久性。相比許多鍵值數據存儲,Redis擁有一套較為豐富的數據類型。Redis可以將數據復制到任意數量的從伺服器。
1.2、Redis優點:
(1)異常快速:Redis的速度非常快,每秒能執行約11萬集合,每秒約81000+條記錄。
(2)支持豐富的數據類型:Redis支持最大多數開發人員已經知道像列表,集合,有序集合,散列數據類型。這使得它非常容易解決各種各樣的問題,因為我們知道哪些問題是可以處理通過它的數據類型更好。
(3)操作都是原子性:所有Redis操作是原子的,這保證了如果兩個客戶端同時訪問的Redis伺服器將獲得更新後的值。
(4)多功能實用工具:Redis是一個多實用的工具,可以在多個用例如緩存,消息,隊列使用(Redis原生支持發布/訂閱),任何短暫的數據,應用程序,如Web應用程序會話,網頁命中計數等。
1.3、Redis缺點:
(1)單線程
(2)耗內存
二、64位windows下Redis安裝
Redis官方是不支持windows的,但是Microsoft Open Tech group 在 GitHub上開發了一個Win64的版本,下載地址:https://github.com/MSOpenTech/redis/releases。注意只支持64位哈。
小寶鴿是下載了Redis-x64-3.0.500.msi進行安裝。安裝過程中全部採取默認即可。
安裝完成之後可能已經幫你開啟了Redis對應的服務,博主的就是如此。查看資源管理如下,說明已經開啟:
已經開啟了對應服務的,我們讓它保持,下面例子需要用到。如果沒有開啟的.,我們命令開啟,進入Redis的安裝目錄(博主的是C:Program FilesRedis),然後如下命令開啟:
redis-server redis.windows.conf
OK,下面我們進行實例。
三、詳細實例
本工程採用的環境:Eclipse + maven + spring + junit
3.1、添加相關依賴(spring+junit+redis依賴),pom.xml:
4.0.0 com.luo redis_project 0.0.1-SNAPSHOT 3.2.8.RELEASE 4.10 org.springframework spring-core ${spring.version} org.springframework spring-webmvc ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-context-support ${spring.version} org.springframework spring-aop ${spring.version} org.springframework spring-aspects ${spring.version} org.springframework spring-tx ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-web ${spring.version} junit junit ${junit.version} test org.springframework spring-test ${spring.version} test org.springframework.data spring-data-redis 1.6.1.RELEASE redis.clients jedis 2.7.3
3.2、spring配置文件application.xml:
<"1.0" encoding="UTF-8"> classpath:properties/*.properties
3.3、Redis配置參數,redis.properties:
#redis中心#綁定的主機地址redis.host=127.0.0.1#指定Redis監聽埠,默認埠為6379redis.port=6379#授權密碼(本例子沒有使用)redis.password=123456 #最大空閑數:空閑鏈接數大於maxIdle時,將進行回收redis.maxIdle=100 #最大連接數:能夠同時建立的「最大鏈接個數」redis.maxActive=300 #最大等待時間:單位msredis.maxWait=1000 #使用連接時,檢測連接是否成功 redis.testOnBorrow=true#當客戶端閑置多長時間後關閉連接,如果指定為0,表示關閉該功能redis.timeout=10000
3.4、添加介面及對應實現RedisTestService.Java和RedisTestServiceImpl.java:
package com.luo.service; public interface RedisTestService { public String getTimestamp(String param);}
package com.luo.service.impl; import org.springframework.stereotype.Service;import com.luo.service.RedisTestService; @Servicepublic class RedisTestServiceImpl implements RedisTestService { public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } }
3.5、本例採用spring aop切面方式進行緩存,配置已在上面spring配置文件中,對應實現為MethodCacheInterceptor.java:
package com.luo.redis.cache; import java.io.Serializable;import java.util.concurrent.TimeUnit;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations; public class MethodCacheInterceptor implements MethodInterceptor { private RedisTemplate redisTemplate; private Long defaultCacheExpireTime = 10l; // 緩存默認的過期時間,這里設置了10秒 public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); try { // 判斷是否有緩存 if (exists(key)) { return getCache(key); } // 寫入緩存 value = invocation.proceed(); if (value != null) { final String tkey = key; final Object tvalue = value; new Thread(new Runnable() { public void run() { setCache(tkey, tvalue, defaultCacheExpireTime); } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null) { return invocation.proceed(); } } return value; } /** * 創建緩存key * * @param targetName * @param methodName * @param arguments */ private String getCacheKey(String targetName, String methodName, Object[] arguments) { StringBuffer sbu = new StringBuffer(); sbu.append(targetName).append("_").append(methodName); if ((arguments != null) && (arguments.length != 0)) { for (int i = 0; i < arguments.length; i++) { sbu.append("_").append(arguments[i]); } } return sbu.toString(); } /** * 判斷緩存中是否有對應的value * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * 讀取緩存 * * @param key * @return */ public Object getCache(final String key) { Object result = null; ValueOperations operations = redisTemplate .opsForValue(); result = operations.get(key); return result; } /** * 寫入緩存 * * @param key * @param value * @return */ public boolean setCache(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations operations = redisTemplate .opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } public void setRedisTemplate( RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }}
3.6、單元測試相關類:
package com.luo.baseTest; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; //指定bean注入的配置文件 @ContextConfiguration(locations = { "classpath:application.xml" }) //使用標準的JUnit @RunWith注釋來告訴JUnit使用Spring TestRunner @RunWith(SpringJUnit4ClassRunner.class) public class SpringTestCase extends { }
package com.luo.service; import org.junit.Test;import org.springframework.beans.factory.annotation.Autowired; import com.luo.baseTest.SpringTestCase; public class RedisTestServiceTest extends SpringTestCase { @Autowired private RedisTestService redisTestService; @Test public void getTimestampTest() throws InterruptedException{ System.out.println("第一次調用:" + redisTestService.getTimestamp("param")); Thread.sleep(2000); System.out.println("2秒之後調用:" + redisTestService.getTimestamp("param")); Thread.sleep(11000); System.out.println("再過11秒之後調用:" + redisTestService.getTimestamp("param")); } }
3.7、運行結果:
四、源碼下載:redis-project().rar
以上就是本文的全部內容,希望對大家的學習有所幫助。