天天看點

Ehcache(05)——緩存的查詢 1.   使Cache可查詢 2       指定可搜尋的屬性 3       查詢

緩存的查詢

目錄

1.    使Cache可查詢

1.1     基于Xml配置

1.2     基于代碼的配置

2     指定可搜尋的屬性

2.1     可查詢屬性類型

2.2     屬性的提取

2.2.1    定義自己的AttributeExtractor

2.2.2    JavaBeanAttributeExtractor

2.2.3    ReflectionAttributeExtractor

2.2.4    DynamicAttributesExtractor

2.3     通過程式指定可查詢屬性

3     查詢

3.1     建立查詢與篩選條件

3.1.1    擷取查詢屬性

3.1.2    篩選類型

3.2     查詢内容

3.3     結果

3.4     統計

3.5     排序

3.6     分組

3.7     讓Query不可變

3.8     對BeanShell的支援

3.9     小結

       Ehcache中為我們提供了可以對Cache中緩存的元素進行查找的方式。其邏輯類似于SQL中的查找。通過給定各種限制條件,我們可以構造各種複雜的查詢,然後傳回結果集,也可以對查詢進行分組和排序等。

       Ehcache中的查詢是針對于Cache而言的。但并不是所有的Cache都可以進行查詢操作,我們需要指定其為一個可查詢的Cache之後才可以對該Cache進行查詢操作。因為在配置Cache的時候有基于xml檔案的配置和基于程式代碼的配置,是以對應的使一個Cache可查詢也有兩種方式。

       當我們的Cache定義是基于Xml檔案的配置時,我們隻需在對應Cache定義下聲明一個子元素searchable即可使目前Cache擁有可查詢的功能。

       基于代碼的配置是通過建立Searchable對象,然後指定需要設定為可查詢Cache對應的CacheConfiguration的Searchable對象為我們建立的Searchable對象即可。

       配置了Cache可查詢後,我們還需要配置目前Cache可以對哪些屬性進行查詢,即可以把哪些屬性作為條件來對Cache進行查詢。在Ehcache中使用一個net.sf.ehcache.search.Attribute來表示一個可查詢的屬性。這些可查詢的屬性可以是我們的key、value或者它們對應的屬性。定義可查詢屬性是通過searchable元素的子元素searchAttribute來定義的,如:

       其中name表示我們所定義的可查詢屬性的名稱,是必須指定的屬性。這裡會通過屬性提取機制提取key或者value中name所對應的屬性,這裡是name屬性,來進行索引。關于屬性提取機制将在後續講解。

       并不是所有的屬性都可以用來作為Cache的可查詢屬性,它必須是以下類型之一:

l  Boolean

l  Byte

l  Short

l  Character

l  Integer

l  Long

l  Float

l  Double

l  String

l  java.util.Date

l  java.sql.Date

l  Enum

       預設情況下,系統會自動把我們存入可查詢Cache中元素的key和value作為可查詢屬性,命名為key和value,當它們是以上可查詢類型時我們可以直接對它們進行查詢。如果不需要預設将我們的key和value作為可查詢屬性的話,我們可以在指定Cache為一個可查詢Cache時指定searchable元素的keys屬性和values屬性為false即可。如:

       當我們的key或者value不是可查詢類型,然而我們又希望對它們進行查詢時,我們就需要把key或者value中的屬性提取出來作為Cache的一個可查詢屬性。這是通過AttributeExtractor來進行的,AttributeExtractor是一個接口,其中隻定義了一個方法Object attributeFor(Element element, String attributeName)。其傳回值必須是可查詢屬性類型之一。當然,傳回null也是可以的。下面我們來看看如何定義自己的AttributeExtractor。

       假設我們有一個名叫userCache的緩存,其中存放的元素值都是一個User對象。而我們的User對象有一個String類型的name屬性。假設我們現在指定了我們的userCache的一個可查詢屬性為user,而其真正對應的内容是我們的Element中存放的value的name。(這個需求可能會比較奇怪)。那麼這個時候我們的AttributeExtractor實作大概會是這個樣子:

       定義好了AttributeExtractor之後,我們要告訴Ehcache,緩存userCache的可查詢屬性user對應的AttributeExtractor是我們定義的UserAttributeExtractor,這隻需要指定searchAttribute元素的class屬性即可。

       之後我們通過user屬性來查詢時就可以通過User對象的name屬性來過濾一些結果集了。如果我們的AttributeExtractor還需要接收其它的參數的話,我們可以通過searchAttribute元素的properties屬性來指定,其對應的參數是鍵值對的形式,中間用等号“=”隔開,多個參數之間用逗号隔開。如:

       我們指定了properties屬性後,我們對應的AttributeExtractor必須給定一個以Properties對象為參數的構造方法才可以接收到這些指定的參數。

       除了定義自己的屬性提取實作類之外,Ehcache還為我們提供了一些實作類。包括KeyObjectAttributeExtractor、ValueObjectAttributeExtractor,這兩個屬性提取器就是預設情況下Ehcache用來把key和value提取為一個可查詢屬性的方式。此外還有JavaBeanAttributeExtractor和ReflectionAttributeExtractor。

       當我們定義一個可查詢屬性searchAttribute隻指定了其name屬性時,系統所使用的AttributeExtractor就是JavaBeanAttributeExtractor。該AttributeExtractor會從元素的key或者value中取searchAttribute的name屬性值所對應的屬性。如果我們有如下這樣一個可查詢緩存的定義,我們的Ehcache在給可查詢屬性address建立索引時就會擷取元素key的address屬性或者value的address屬性來作為查詢屬性address的值。

       注意:使用JavaBeanAttributeExtractor時,如果key和value中都包含可查詢屬性,則系統會抛出異常,如果都不包含的話也會抛出異常。

       當我們定義一個可查詢屬性searchAttribute時指定了expression屬性時,系統就會使用ReflectionAttributeExtractor來提取屬性的值。此屬性提取器是通過反射來提取屬性值的。expression必須以key、value或element開始,然後中間以點“.”來連接配接它們所對應的屬性或方法,以及屬性的屬性,方法的方法。key表示元素的key,value表示元素的value,element表示元素本身。下面來看幾個示例。

       1.查詢屬性address的值是對應的value的address屬性。

       2.查詢屬性address的值是對應的value的extraInfo屬性的getAddress()方法的傳回值。

       3.查詢屬性hitCount的值是對應的element的getHitCount()方法的傳回值。

       之前介紹的AttributeExtractor都是在Cache執行個體化之前定義的,其會在Cache執行個體化時初始化這些可查詢屬性。而DynamicAttributesExtractor允許我們在Cache執行個體化後添加可查詢屬性。DynamicAttributesExtractor是一個接口,它跟AttributeExtractor接口沒有任何關系。該接口中僅定義了一個方法attributesFor(),該方法将接收一個Element對象作為參數,然後傳回一個将作為可查詢屬性的Map,該Map的key對應可查詢屬性的名稱,而value則對應可查詢屬性的值。那麼我們在實作DynamicAttributesExtractor接口時隻需要實作attributesFor()方法即可。

       使用DynamicAttributeExtractor時我們的Cache對應的Searchable必須是支援該提取器才行,這是通過Searchable對象的allowDynamicIndexing屬性來指定的,使用xml配置時該屬性是直接配置在searchable元素上的,而使用程式來定義時則需要通過Searchable對象來指定了。之後我們需要把它注冊給我們的Cache。通過Cache的registerDynamicAttributesExtractor()方法我們就可以給Cache注冊一個動态的屬性提取器了,該提取器将在往Cache中put或者replace元素時被調用。通過文字說明會比較抽象,接下來我們來看一個相應的示例。

       假設我們定義了如下這樣一個專門用來緩存User的Cache,其中User中含有屬性name。我們在定義該Cache的時候即指定了其是一個可查詢的Cache,同時通過指定allowDynamicIndexing為true使其支援動态屬性提取,我們還給該Cache指定了一個可查詢屬性name。

       接下來我們将在該Cache初始化之後注冊一個DynamicAttributesExtractor,用于索引元素被查詢到的次數hitCount。代碼如下所示,我們在userCache初始化後給其注冊了一個DynamicAttributesExtractor,在DynamicAttributesExtractor實作類中我們實作了attributesFor方法,在該方法體内我們構造了一個Map,并往其中放入了一個key為hitCount的元素。當我們往userCache中put或者replace元素的時候,就會觸發我們注冊的DynamicAttributesExtractor的attributesFor方法,然後Ehcache會對傳回的動态可查詢屬性hitCount進行索引。在下面的代碼中,我們的在給userCache注冊了DynamicAttributesExtractor之後,馬上列出其中包含的可查詢屬性,這個時候肯定隻會包含預定義好的key、value和name,因為我們注冊的DynamicAttributesExtractor還沒有被執行。之後往其中放入元素之後,userCache中包含的可查詢屬性才會有通過DynamicAttributesExtractor傳回的hitCount。

       一個Cache隻能注冊有一個DynamicAttributesExtractor,當同時注冊多個時,後者會将前者覆寫。但是DynamicAttributesExtractor和其它AttributeExtractor是可以并存的,是以因為其它AttributeExtractor是在Cache初始化前定義的,是以DynamicAttributesExtractor不能傳回已經通過AttributeExtractor提取過的同名屬性。

       通過前面的内容我們知道設定可查詢屬性時除了DynamicAttributesExtractor可以在Cache初始化後再添加可查詢屬性外,我們的可查詢屬性必須是在Cache初始化之前進行指定,否則在對Cache進行查詢時我們就不能使用該查詢屬性進行查詢。如下面這一段代碼,我們在Cache初始化後通過擷取其配置資訊,再往其對應的Searchalbe對象中新增一個名叫hello的查詢屬性,那麼我們在今後對該Cache進行查詢時将不能使用hello屬性進行查詢。

       由于定義非動态查詢屬性時需要在Cache初始化時定義,是以當我們需要在程式中定義查詢屬性時對應的Cache也需要是在程式中聲明的才行。下面是在程式中指定可查詢屬性的一個示例。

       在Ehcache中是通過一個net.sf.ehcache.search.Query對象來表示一個查詢的,通過該對象我們可以對緩存中的元素進行查詢,查詢條件就是我們之前定義好的可查詢屬性,而查詢結果可以是緩存的key、value或可查詢屬性,也可以是針對于可查詢屬性的一些統計結果。

       在對Cache進行查詢前我們需要先建立一個Query對象。Query對象是通過EhCache接口定義的createQuery()方法建立的,Cache類對它進行了實作。有了Query對象之後,我們需要使用Query對象的addCriteria(Criteria criteria)方法給該Query對象添加一些限制條件來對其中緩存的元素進行篩選,否則傳回的結果将是針對于所有的緩存元素的。

       Criteria是一個接口,在net.sf.ehcache.search.expression定義了其一系列的實作類,我們也可以直接通過new一個Criteria實作類的執行個體來對Query結果進行篩選。但通常我們不需要這樣做,因為Ehcache中已經為我們實作了的Criteria通常已經可以滿足我們的需求了。Ehcache中代表查詢屬性的Attribute類已經為我們提供了擷取針對于該屬性的各種Criteria的方法。好,現在我們已經知道了可以通過查詢屬性直接擷取到針對于該屬性的限制Criteria對象,那麼我們該如何擷取查詢屬性呢?

       擷取查詢屬性Attribute主要有兩種方式,一種是直接new一個Attribute執行個體對象,另一種是通過Ehcache接口定義的getSearchAttribute(String attrName)擷取到可查詢緩存中對應屬性名稱的可查詢屬性對象Attribute。常用的還是通過getSearchAttribute(String attrName)方法來擷取對應的查詢屬性Attribute。當調用可查詢Cache的getSearchAttribute(String attrName)方法來擷取目前緩存的可查詢屬性時,如果對應名稱的可查詢屬性不存在,則會抛出異常。

       Attribute類使用了泛型定義,其表示目前屬性值的類型。

       有了可查詢屬性Attribute之後,我們就可以通過Attribute類定義的一系列方法擷取到目前Attribute的某種限制,進而對Query的查詢結果進行篩選。如我們要篩選name為“name1”的查詢結果時我們可以通過name.eq(“name1”)來進行篩選。

       接下來我們來看一下Attribute類為我們提供的擷取對應Criteria的方法有哪些。

Attribute方法

對應Criteria實作類

描述

between

Between

屬性值在給定的範圍之間

in

InCollection

在給定的集合之中

ne

NotEqualTo

不等于給定的值

eq

EqualTo

等于給定的值

lt

LessThan

小于給定的值

le

LessThanOrEqual

小于或等于給定的值

gt

GreaterThan

大于給定的值

ge

GreaterThanOrEqual

大于或等于給定的值

ilike

ILike

比對給定的表達式,表達式中可以使用“*”來代表任意多個字元,使用“?”來代表任意一個字元

notIlike

NotILike

不比對給定的表達式

isNull

IsNull

等于null

notNull

NotNull

不等于null

       那當我們要實作與或非的邏輯時怎麼辦呢?Criteria為我們提供了對應的方法,分别對應and(Criteria criteria)方法、or(Criteria criteria)方法和not()方法,然後這三個方法的傳回結果還是一個Criteria,它們對應的Criteria實作類分别為And、Or和Not。當我們使用Query的addCriteria(Criteria criteria)方法來添加一個篩選條件時預設都是對應的and操作。

       下面我們來看一些使用Criteria的例子。先假設我們有如下定義的一個Cache,其中存放的元素的value都是一個User對象,下面将給出一些針對于該Cache使用Criteria進行篩選查詢的一些示例。

1、年齡在25歲到35歲之間且屬于機關002的。

2、屬于機關002或者機關003,手機号碼以137開始且年齡大于35歲的。

3、不屬于機關002且年齡小于30的。

       一個Query在查詢之前,我們必須告訴它需要查詢什麼内容,也就是說查詢的結果中會包含哪些資訊。如果在執行查詢操作之前沒有告訴Query我們要查詢什麼内容,Ehcache将抛出異常。可以查詢的内容包括緩存中存入元素的key、value,可查詢屬性對應的值,以及針對于目前查詢結果中某個可查詢屬性的統計資訊。針對于這四種可以查詢内容Query中提供了四個include方法來表示目前Query的查詢結果中會包含對應的内容。下面用一個表格來做個展示。

Query方法

includeKeys()

查詢結果中包含所存元素的key

includeValues()

查詢結果中包含所存元素的value

includeAttribute(Attribute<?>... attributes)

查詢結果中要包含的可查詢屬性

includeAggregator(Aggregator... aggregators)

查詢結果中所要包含的統計資訊,關于Aggregator将在後文介紹統計的時候進行講解

       如下的代碼表示我們的查詢結果中會包含元素的key、可查詢屬性name和age對應的值。

       在實際應用中,為了讓我們的程式具有更好的性能,我們的查詢結果最好隻包含我們需要的資訊。如隻需要擷取某個屬性的值就不必傳回整個value。

       有了Query之後我們就可以來執行對應的查詢操作,擷取傳回的查詢結果。通過調用Query的execute()方法就可以對目前Query執行查詢操作,并擷取其傳回的結果。Ehcache中使用一個Results接口來代表一個Query的查詢結果,使用Result接口來代表對應的一條記錄。Results中定義了一個方法all()用于傳回查詢出來的所有Result組成的List,查詢的緩存中有多少元素滿足查詢條件,查詢結果Results中就會包含多少個Result對象。Result中定義有getKey()、getValue()、getAttribute()和getAggregatorResults()方法用于擷取查詢結果中對應元素的key、value、可查詢屬性對應的值,以及針對于目前查詢的統計資訊組成的List。如果查詢結果中不包含對應的資訊,那麼在Result調用對應方法擷取資訊時将抛出異常。Results針對于查詢結果中是否包含這四方面的資訊給我們提供了四個has方法:hasKeys()、hasValues()、hasAttributes()和hasAggregators()。Results和Result這兩個接口Ehcache中都已經存在對應的實作了,我們在使用時隻要直接利用接口來進行操作就可以了。

       當然,如果你已經清楚的知道了查詢結果中已經包含了key時你在擷取key前就可以不用調用Results的hasKeys()方法進行判斷了,其它結果也一樣。

       Results中的all()方法可以傳回目前查詢的結果中的所有Result組成的List。另外,Results中還提供了一個range(int start, int count)方法用于擷取目前結果集的一個子集,其底層預設實作使用的是List的subList()方法。該方法可以用于對查詢結果的分頁操作。

       預設情況下,我們在對Cache進行查詢時,查詢結果将傳回所有滿足查詢條件的記錄。當傳回的記錄非常多時,系統可能會因為記憶體不足而報錯。Query中定義了一個maxResults(int maxResults)方法用于限制目前查詢傳回查詢結果的最大記錄數。

       需要注意的是由于元素過期的問題,我們查詢結果中的元素不一定還存在。

       當我們利用完Results之後,我們需要通過調用Results的discard()方法來釋放資源。

       Ehcache為我們提供了一個Aggregator接口用于在查詢過程中對某個查詢屬性進行統計。我們可以實作自己的Aggregator,也可以使用Ehcache為我們提供的實作類。Ehcache中已經為我們提供了五個Aggregator實作類,分别是Min、Max、Sum、Count和Average。看了名稱我應該就知道這五個Aggregator分别是做什麼用的。Min是求最小值、Max是求最大值、Sum是求和、Count是計數、Average是求平均值。那麼在使用這五個Aggregator時也是非常友善的,因為我們的Attribute已經為我們針對這五個Aggregator定義了對應的方法。方法名稱就是對應Aggregator實作類簡稱的首字母小寫,如Min在Attribute中就對應min()方法。

       當我們需要對某個查詢屬性進行統計時,我們需要把對應的Aggregator通過調用Query的includeAggregator()方法添加到查詢的結果中。

       當我們的查詢結果中隻包含有統計資訊時,我們的查詢結果Results中隻會有一條記錄,即一個Result對象。當包含其它資訊時查詢結果就可能會有多條記錄,而且每條記錄中都會包含有對應的統計資訊。

       Ehcache中對于Cache的查詢也是可以進行排序的,這是通過Query的addOrderBy()方法來指定的。該方法接收兩個參數,第一個參數表示需要進行排序的屬性Attribute,第二個參數是排序的方向Direction。Direction有兩個可選值,Direction.ASCENDING和Direction.DESCENDING。當需要對多個屬性進行排序時則需要調用多次addOrderBy()方法。

       Ehcache也支援對查詢的緩存進行分組。這是通過Query的addGroupBy()方法來定義的,該方法接收一個Attribute作為參數,表示要對哪個Attribute進行分組,當需要對多個Attribute進行分組時,則需要調用多次addGroupBy()方法。使用分組的文法基本上跟SQL裡面分組的文法是一樣的,當使用分組時查詢結果隻能包含分組的屬性和統計資訊,統計資訊是對分組後的情況進行統計。唯一不同的是Ehcahce中查詢分組時無法對分組後的情況進行篩選。

       以下是一個通過機關編碼進行分組統計各機關員工的平均年齡、最大年齡和員工的人數的示例。

       預設情況下,我們的Query可以在執行後修改某些屬性後繼續查詢。但是一旦我們調用了Query的end()方法之後我們将不能夠再更改Query的一些屬性。這包括調用include來定義傳回結果中需要包含的資訊、指定排序的屬性、指定分組的屬性、添加Criteria限制條件和調用maxResults()方法指定最大傳回記錄數。

       BeanShell是用Java寫的,能夠對Java字元串表達式進行解釋執行的一個工具。如果在實際應用中我們需要讓使用者來自定義查詢的腳本時,我們就可以使用BeanShell來對查詢腳本進行解釋執行了。使用BeanShell前我們需加入BeanShell的jar包到類路徑,筆者下面的示例中使用的是BeanShell2.0的第4個測試版本。

       關于BeanShell的更多了解請通路BeanShell的官方網站www.beanshell.org。

       縱觀整個Ehcahce中對于Cache的查詢Query,我們可以發現其基本的邏輯和規則與SQL查詢是一樣的。可以進行篩選、選擇要查詢的結果、統計、排序和分組。Ehcache中的查詢也是先通過Criteria進行篩選,再進行分組和排序。

(注:本文是基于ehcache2.8.1所寫)