https://www.iteye.com/blog/elim-2117505
緩存的查詢
目錄
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中的查找。通過給定各種限制條件,我們可以構造各種復雜的查詢,然后返回結果集,也可以對查詢進行分組和排序等。
1. 使Cache可查詢
Ehcache中的查詢是針對於Cache而言的。但並不是所有的Cache都可以進行查詢操作,我們需要指定其為一個可查詢的Cache之后才可以對該Cache進行查詢操作。因為在配置Cache的時候有基於xml文件的配置和基於程序代碼的配置,所以對應的使一個Cache可查詢也有兩種方式。
1.1 基於Xml配置
當我們的Cache定義是基於Xml文件的配置時,我們只需在對應Cache定義下聲明一個子元素searchable即可使當前Cache擁有可查詢的功能。
- <cache name="searchableCache" maxBytesLocalHeap="100M">
- <searchable/>
- </cache>
1.2 基於代碼的配置
基於代碼的配置是通過新建Searchable對象,然后指定需要設置為可查詢Cache對應的CacheConfiguration的Searchable對象為我們新建的Searchable對象即可。
- public void test() {
- CacheManager cacheManager = CacheManager.create();
- CacheConfiguration cacheConfig = new CacheConfiguration();
- cacheConfig.name("cache1").maxBytesLocalHeap(100, MemoryUnit.MEGABYTES);
- Searchable searchable = new Searchable();
- //指定Cache的Searchable對象。
- cacheConfig.searchable(searchable);
- //如下指定也行
- // cacheConfig.addSearchable(searchable);
- Cache cache1 = new Cache(cacheConfig);
- cacheManager.addCache(cache1);
- }
2 指定可搜索的屬性
配置了Cache可查詢后,我們還需要配置當前Cache可以對哪些屬性進行查詢,即可以把哪些屬性作為條件來對Cache進行查詢。在Ehcache中使用一個net.sf.ehcache.search.Attribute來表示一個可查詢的屬性。這些可查詢的屬性可以是我們的key、value或者它們對應的屬性。定義可查詢屬性是通過searchable元素的子元素searchAttribute來定義的,如:
- <cache name="userCache" maxBytesLocalHeap="50M">
- <searchable>
- <searchAttribute name="name"/>
- </searchable>
- </cache>
其中name表示我們所定義的可查詢屬性的名稱,是必須指定的屬性。這里會通過屬性提取機制提取key或者value中name所對應的屬性,這里是name屬性,來進行索引。關於屬性提取機制將在后續講解。
2.1 可查詢屬性類型
並不是所有的屬性都可以用來作為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即可。如:
- <cache name="searchableCache" maxBytesLocalHeap="100M">
- <searchable keys="false" values="false"/>
- </cache>
2.2 屬性的提取
當我們的key或者value不是可查詢類型,然而我們又希望對它們進行查詢時,我們就需要把key或者value中的屬性提取出來作為Cache的一個可查詢屬性。這是通過AttributeExtractor來進行的,AttributeExtractor是一個接口,其中只定義了一個方法Object attributeFor(Element element, String attributeName)。其返回值必須是可查詢屬性類型之一。當然,返回null也是可以的。下面我們來看看如何定義自己的AttributeExtractor。
2.2.1 定義自己的AttributeExtractor
假設我們有一個名叫userCache的緩存,其中存放的元素值都是一個User對象。而我們的User對象有一個String類型的name屬性。假設我們現在指定了我們的userCache的一個可查詢屬性為user,而其真正對應的內容是我們的Element中存放的value的name。(這個需求可能會比較奇怪)。那么這個時候我們的AttributeExtractor實現大概會是這個樣子:
- public class UserAttributeExtractor implements AttributeExtractor {
- @Override
- public Object attributeFor(Element element, String attributeName)
- throws AttributeExtractorException {
- User user = (User) element.getObjectValue();
- return user.getName();
- }
- }
定義好了AttributeExtractor之后,我們要告訴Ehcache,緩存userCache的可查詢屬性user對應的AttributeExtractor是我們定義的UserAttributeExtractor,這只需要指定searchAttribute元素的class屬性即可。
- <cache name="userCache" maxBytesLocalHeap="50M">
- <searchable>
- <searchAttribute name="user" class="com.xxx.UserAttributeExtractor"/>
- </searchable>
- </cache>
之后我們通過user屬性來查詢時就可以通過User對象的name屬性來過濾一些結果集了。如果我們的AttributeExtractor還需要接收其它的參數的話,我們可以通過searchAttribute元素的properties屬性來指定,其對應的參數是鍵值對的形式,中間用等號“=”隔開,多個參數之間用逗號隔開。如:
- <cache name="userCache" maxBytesLocalHeap="50M">
- <searchable>
- <searchAttribute name="user" class="com.xxx.UserAttributeExtractor" properties="a=1,b=2"/>
- </searchable>
- </cache>
我們指定了properties屬性后,我們對應的AttributeExtractor必須給定一個以Properties對象為參數的構造方法才可以接收到這些指定的參數。
除了定義自己的屬性提取實現類之外,Ehcache還為我們提供了一些實現類。包括KeyObjectAttributeExtractor、ValueObjectAttributeExtractor,這兩個屬性提取器就是默認情況下Ehcache用來把key和value提取為一個可查詢屬性的方式。此外還有JavaBeanAttributeExtractor和ReflectionAttributeExtractor。
2.2.2 JavaBeanAttributeExtractor
當我們定義一個可查詢屬性searchAttribute只指定了其name屬性時,系統所使用的AttributeExtractor就是JavaBeanAttributeExtractor。該AttributeExtractor會從元素的key或者value中取searchAttribute的name屬性值所對應的屬性。如果我們有如下這樣一個可查詢緩存的定義,我們的Ehcache在給可查詢屬性address建立索引時就會獲取元素key的address屬性或者value的address屬性來作為查詢屬性address的值。
- <cache name="searchableCache" maxBytesLocalHeap="100M">
- <searchable keys="false" values="false">
- <searchAttribute name="address"/>
- </searchable>
- </cache>
注意:使用JavaBeanAttributeExtractor時,如果key和value中都包含可查詢屬性,則系統會拋出異常,如果都不包含的話也會拋出異常。
2.2.3 ReflectionAttributeExtractor
當我們定義一個可查詢屬性searchAttribute時指定了expression屬性時,系統就會使用ReflectionAttributeExtractor來提取屬性的值。此屬性提取器是通過反射來提取屬性值的。expression必須以key、value或element開始,然后中間以點“.”來連接它們所對應的屬性或方法,以及屬性的屬性,方法的方法。key表示元素的key,value表示元素的value,element表示元素本身。下面來看幾個示例。
1.查詢屬性address的值是對應的value的address屬性。
- <cache name="searchableCache" maxBytesLocalHeap="100M">
- <searchable keys="false" values="false">
- <searchAttribute name="address" expression="value.address"/>
- </searchable>
- </cache>
2.查詢屬性address的值是對應的value的extraInfo屬性的getAddress()方法的返回值。
- <cache name="searchableCache" maxBytesLocalHeap="100M">
- <searchable keys="false" values="false">
- <searchAttribute name="address" expression="value.extraInfo.getAddress()"/>
- </searchable>
- </cache>
3.查詢屬性hitCount的值是對應的element的getHitCount()方法的返回值。
- <cache name="searchableCache" maxBytesLocalHeap="100M">
- <searchable keys="false" values="false">
- <searchAttribute name="hitCount" expression="element.getHitCount()"/>
- </searchable>
- </cache>
2.2.4 DynamicAttributesExtractor
之前介紹的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 name="userCache" maxBytesLocalHeap="50M">
- <searchable allowDynamicIndexing="true">
- <searchAttribute name="name" expression="value.getName()"/>
- </searchable>
- </cache>
接下來我們將在該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。
- @Test
- public void dynamicExtractor() {
- CacheManager cacheManager = CacheManager.create();
- Cache userCache = cacheManager.getCache("userCache");
- userCache.registerDynamicAttributesExtractor(new DynamicAttributesExtractor() {
- @Override
- public Map<String, Object> attributesFor(Element element) {
- Map<String, Object> attrMap = new HashMap<String, Object>();
- attrMap.put("hitCount", element.getHitCount());
- return attrMap;
- }
- });
- this.listSearchableAttrs(userCache); //key、value和name
- userCache.put(new Element("1", new User()));
- this.listSearchableAttrs(userCache); //key、value、name和hitCount
- }
- /**
- * 輸出當前Ehcache中可查詢的屬性
- * @param cache
- */
- private void listSearchableAttrs(Ehcache cache) {
- Set<Attribute> attrSet = cache.getSearchAttributes();
- for (Attribute attr : attrSet) {
- System.out.println(attr.getAttributeName());
- }
- }
一個Cache只能注冊有一個DynamicAttributesExtractor,當同時注冊多個時,后者會將前者覆蓋。但是DynamicAttributesExtractor和其它AttributeExtractor是可以並存的,所以因為其它AttributeExtractor是在Cache初始化前定義的,所以DynamicAttributesExtractor不能返回已經通過AttributeExtractor提取過的同名屬性。
2.3 通過程序指定可查詢屬性
通過前面的內容我們知道設置可查詢屬性時除了DynamicAttributesExtractor可以在Cache初始化后再添加可查詢屬性外,我們的可查詢屬性必須是在Cache初始化之前進行指定,否則在對Cache進行查詢時我們就不能使用該查詢屬性進行查詢。如下面這一段代碼,我們在Cache初始化后通過獲取其配置信息,再往其對應的Searchalbe對象中新增一個名叫hello的查詢屬性,那么我們在今后對該Cache進行查詢時將不能使用hello屬性進行查詢。
- @Test
- public void setSearchAttrInProgram() {
- CacheManager cacheManager = CacheManager.create();
- Cache cache = cacheManager.getCache("searchableCache");
- CacheConfiguration cacheConfig = cache.getCacheConfiguration();
- Searchable searchable = cacheConfig.getSearchable();
- SearchAttribute searchAttribute = new SearchAttribute();
- searchAttribute.name("hello");
- searchable.addSearchAttribute(searchAttribute);
- this.listSearchableAttrs(cache);
- }
由於定義非動態查詢屬性時需要在Cache初始化時定義,所以當我們需要在程序中定義查詢屬性時對應的Cache也需要是在程序中聲明的才行。下面是在程序中指定可查詢屬性的一個示例。
- @Test
- public void setSearchAttrInProgram() {
- CacheManager cacheManager = CacheManager.create();
- CacheConfiguration cacheConfig = new CacheConfiguration();
- cacheConfig.name("cacheName").maxBytesLocalHeap(100, MemoryUnit.MEGABYTES);
- //新建一個Searchable對象
- Searchable searchable = new Searchable();
- //給Cache配置Searchable對象,表明該Cache是一個可查詢的Cache
- cacheConfig.searchable(searchable);
- //新建一個查詢屬性
- SearchAttribute searchAttribute = new SearchAttribute();
- //指定查詢屬性的名稱和屬性提取器的類名
- searchAttribute.name("查詢屬性名稱");
- //searchAttribute.className("屬性提取器的類名");
- //Searchalbe對象添加查詢屬性
- searchable.addSearchAttribute(searchAttribute);
- //使用CacheConfig創建Cache對象
- Cache cache = new Cache(cacheConfig);
- //把Cache對象納入CacheManager的管理中
- cacheManager.addCache(cache);
- this.listSearchableAttrs(cache);
- }
3 查詢
在Ehcache中是通過一個net.sf.ehcache.search.Query對象來表示一個查詢的,通過該對象我們可以對緩存中的元素進行查詢,查詢條件就是我們之前定義好的可查詢屬性,而查詢結果可以是緩存的key、value或可查詢屬性,也可以是針對於可查詢屬性的一些統計結果。
3.1 創建查詢與篩選條件
在對Cache進行查詢前我們需要先創建一個Query對象。Query對象是通過EhCache接口定義的createQuery()方法創建的,Cache類對它進行了實現。有了Query對象之后,我們需要使用Query對象的addCriteria(Criteria criteria)方法給該Query對象添加一些限制條件來對其中緩存的元素進行篩選,否則返回的結果將是針對於所有的緩存元素的。
- @Test
- public void search () {
- CacheManager cacheManager = CacheManager.create();
- Cache userCache = cacheManager.getCache("userCache");
- User user;
- for (int i=0; i<10; i++) {
- user = new User(i, "name"+(i%2), 30+i);
- userCache.put(new Element(user.getId(), user));
- }
- Query query = userCache.createQuery();
- }
Criteria是一個接口,在net.sf.ehcache.search.expression定義了其一系列的實現類,我們也可以直接通過new一個Criteria實現類的實例來對Query結果進行篩選。但通常我們不需要這樣做,因為Ehcache中已經為我們實現了的Criteria通常已經可以滿足我們的需求了。Ehcache中代表查詢屬性的Attribute類已經為我們提供了獲取針對於該屬性的各種Criteria的方法。好,現在我們已經知道了可以通過查詢屬性直接獲取到針對於該屬性的限制Criteria對象,那么我們該如何獲取查詢屬性呢?
3.1.1 獲取查詢屬性
獲取查詢屬性Attribute主要有兩種方式,一種是直接new一個Attribute實例對象,另一種是通過Ehcache接口定義的getSearchAttribute(String attrName)獲取到可查詢緩存中對應屬性名稱的可查詢屬性對象Attribute。常用的還是通過getSearchAttribute(String attrName)方法來獲取對應的查詢屬性Attribute。當調用可查詢Cache的getSearchAttribute(String attrName)方法來獲取當前緩存的可查詢屬性時,如果對應名稱的可查詢屬性不存在,則會拋出異常。
- CacheManager cacheManager = CacheManager.create();
- Cache cache = cacheManager.getCache("userCache");
- Attribute<String> name = cache.getSearchAttribute("name");
Attribute類使用了泛型定義,其表示當前屬性值的類型。
3.1.2 篩選類型
有了可查詢屬性Attribute之后,我們就可以通過Attribute類定義的一系列方法獲取到當前Attribute的某種限制,從而對Query的查詢結果進行篩選。如我們要篩選name為“name1”的查詢結果時我們可以通過name.eq(“name1”)來進行篩選。
- public void search2() {
- CacheManager cacheManager = CacheManager.create();
- Cache userCache = cacheManager.getCache("userCache");
- User user;
- for (int i=0; i<10; i++) {
- user = new User(i, "name"+(i%2), 30+i);
- userCache.put(new Element(user.getId(), user));
- }
- //獲取名稱為name的可查詢屬性Attribute對象
- Attribute<String> name = userCache.getSearchAttribute("name");
- //創建一個用於查詢的Query對象
- Query query = userCache.createQuery();
- //給當前query添加一個篩選條件——可查詢屬性name的值等於“name1”
- query.addCriteria(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進行篩選查詢的一些示例。
- <cache name="userCache" maxBytesLocalHeap="50M">
- <searchable>
- <searchAttribute name="name" expression="value.getName()"/>
- <searchAttribute name="age"/>
- <searchAttribute name="unitNo" expression="value.unit.unitNo"/>
- <searchAttribute name="unitName" expression="value.unit.getUnitName()"/>
- <searchAttribute name="mobile" expression="value.getMobile()"/>
- <searchAttribute name="hitCount" expression="element.getHitCount()"/>
- </searchable>
- </cache>
1、年齡在25歲到35歲之間且屬於單位002的。
- Attribute<Integer> age = userCache.getSearchAttribute("age");
- Attribute<String> unitNo = userCache.getSearchAttribute("unitNo");
- query.addCriteria(age.between(25, 35).and(unitNo.eq("002")));
- //或者使用兩次addCriteria
- // query.addCriteria(age.between(25, 35)).addCriteria(unitNo.eq("002"));
2、屬於單位002或者單位003,手機號碼以137開始且年齡大於35歲的。
- Attribute<Integer> age = userCache.getSearchAttribute("age");
- Attribute<String> unitNo = userCache.getSearchAttribute("unitNo");
- Attribute<String> mobile = userCache.getSearchAttribute("mobile");
- ry.addCriteria(age.gt(35).and(unitNo.eq("002").or(unitNo.eq("003"))).and(mobile.ilike("137*")));
3、不屬於單位002且年齡小於30的。
- Attribute<Integer> age = userCache.getSearchAttribute("age");
- Attribute<String> unitNo = userCache.getSearchAttribute("unitNo");
- query.addCriteria(unitNo.ne("002").and(age.lt(30)));
- //或者使用not()方法
- query.addCriteria(unitNo.eq("002").not().and(age.lt(30)));
3.2 查詢內容
一個Query在查詢之前,我們必須告訴它需要查詢什么內容,也就是說查詢的結果中會包含哪些信息。如果在執行查詢操作之前沒有告訴Query我們要查詢什么內容,Ehcache將拋出異常。可以查詢的內容包括緩存中存入元素的key、value,可查詢屬性對應的值,以及針對於當前查詢結果中某個可查詢屬性的統計信息。針對於這四種可以查詢內容Query中提供了四個include方法來表示當前Query的查詢結果中會包含對應的內容。下面用一個表格來做個展示。
Query方法 |
描述 |
includeKeys() |
查詢結果中包含所存元素的key |
includeValues() |
查詢結果中包含所存元素的value |
includeAttribute(Attribute<?>... attributes) |
查詢結果中要包含的可查詢屬性 |
includeAggregator(Aggregator... aggregators) |
查詢結果中所要包含的統計信息,關於Aggregator將在后文介紹統計的時候進行講解 |
如下的代碼表示我們的查詢結果中會包含元素的key、可查詢屬性name和age對應的值。
- Attribute<String> name = userCache.getSearchAttribute("name");
- Attribute<Integer> age = userCache.getSearchAttribute("age");
- query.includeAttribute(name, age);
在實際應用中,為了讓我們的程序具有更好的性能,我們的查詢結果最好只包含我們需要的信息。如只需要獲取某個屬性的值就不必返回整個value。
3.3 結果
有了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中都已經存在對應的實現了,我們在使用時只要直接利用接口來進行操作就可以了。
- //執行查詢操作,返回查詢結果Results
- Results results = query.execute();
- //獲取Results中包含的所有的Result對象
- List<Result> resultList = results.all();
- if (resultList != null && !resultList.isEmpty()) {
- for (Result result : resultList) {
- //結果中包含key時可以獲取key
- if (results.hasKeys()) {
- result.getKey();
- }
- //結果中包含value時可以獲取value
- if (results.hasValues()) {
- result.getValue();
- }
- //結果中包含屬性時可以獲取某個屬性的值
- if (results.hasAttributes()) {
- Attribute<String> attribute = userCache.getSearchAttribute("name");
- result.getAttribute(attribute);
- }
- //結果中包含統計信息時可以獲取統計信息組成的List
- if (results.hasAggregators()) {
- result.getAggregatorResults();
- }
- }
- }
當然,如果你已經清楚的知道了查詢結果中已經包含了key時你在獲取key前就可以不用調用Results的hasKeys()方法進行判斷了,其它結果也一樣。
Results中的all()方法可以返回當前查詢的結果中的所有Result組成的List。另外,Results中還提供了一個range(int start, int count)方法用於獲取當前結果集的一個子集,其底層默認實現使用的是List的subList()方法。該方法可以用於對查詢結果的分頁操作。
默認情況下,我們在對Cache進行查詢時,查詢結果將返回所有滿足查詢條件的記錄。當返回的記錄非常多時,系統可能會因為內存不足而報錯。Query中定義了一個maxResults(int maxResults)方法用於限制當前查詢返回查詢結果的最大記錄數。
需要注意的是由於元素過期的問題,我們查詢結果中的元素不一定還存在。
當我們利用完Results之后,我們需要通過調用Results的discard()方法來釋放資源。
3.4 統計
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()方法添加到查詢的結果中。
- //創建一個用於查詢的Query對象
- Query query = userCache.createQuery();
- Attribute<Integer> age = userCache.getSearchAttribute("age");
- //查詢結果中包含age的平均值和age的最大值
- query.includeAggregator(age.average(), age.max());
- Results results = query.execute();
- List<Result> resultList = results.all();
- if (resultList != null && !resultList.isEmpty()) {
- //每一個查詢結果Result中都會包含對查詢結果的統計信息。
- Result result = resultList.get(0);
- //多個統計信息將會組成一個List進行返回
- List<Object> aggregatorResults = result.getAggregatorResults();
- Number averageAge = (Number)aggregatorResults.get(0);
- Integer maxAge = (Integer)aggregatorResults.get(1);
- System.out.println(averageAge + "---" + maxAge);
- }
當我們的查詢結果中只包含有統計信息時,我們的查詢結果Results中只會有一條記錄,即一個Result對象。當包含其它信息時查詢結果就可能會有多條記錄,而且每條記錄中都會包含有對應的統計信息。
3.5 排序
Ehcache中對於Cache的查詢也是可以進行排序的,這是通過Query的addOrderBy()方法來指定的。該方法接收兩個參數,第一個參數表示需要進行排序的屬性Attribute,第二個參數是排序的方向Direction。Direction有兩個可選值,Direction.ASCENDING和Direction.DESCENDING。當需要對多個屬性進行排序時則需要調用多次addOrderBy()方法。
- Attribute<String> unitNo = userCache.getSearchAttribute("unitNo");
- Attribute<Integer> age = userCache.getSearchAttribute("age");
- //查詢結果按部門編號的升序和年齡的降序進行排列
- query.addOrderBy(unitNo, Direction.ASCENDING).addOrderBy(age, Direction.DESCENDING);
3.6 分組
Ehcache也支持對查詢的緩存進行分組。這是通過Query的addGroupBy()方法來定義的,該方法接收一個Attribute作為參數,表示要對哪個Attribute進行分組,當需要對多個Attribute進行分組時,則需要調用多次addGroupBy()方法。使用分組的語法基本上跟SQL里面分組的語法是一樣的,當使用分組時查詢結果只能包含分組的屬性和統計信息,統計信息是對分組后的情況進行統計。唯一不同的是Ehcahce中查詢分組時無法對分組后的情況進行篩選。
以下是一個通過單位編碼進行分組統計各單位員工的平均年齡、最大年齡和員工的人數的示例。
- //創建一個用於查詢的Query對象
- Query query = userCache.createQuery();
- Attribute<String> unitNo = userCache.getSearchAttribute("unitNo");
- Attribute<Integer> age = userCache.getSearchAttribute("age");
- //對單位編號進行分組
- query.addGroupBy(unitNo);
- //各單位年齡的平均值、最大值以及人數。
- query.includeAggregator(age.average(), age.max(), age.count());
- //查詢結果中還包含單位編碼
- query.includeAttribute(unitNo);
- Results results = query.execute();
- List<Result> resultList = results.all();
- if (resultList != null && !resultList.isEmpty()) {
- for (Result result : resultList) {
- String unitNoVal = result.getAttribute(unitNo);
- //多個統計信息將會組成一個List進行返回
- List<Object> aggregatorResults = result.getAggregatorResults();
- Number averageAge = (Number)aggregatorResults.get(0);
- Integer maxAge = (Integer)aggregatorResults.get(1);
- Integer count = (Integer)aggregatorResults.get(2);
- System.out.println("單位編號:" + unitNoVal + "---" + averageAge + "," + maxAge + "," + count);
- }
- }
3.7 讓Query不可變
默認情況下,我們的Query可以在執行后修改某些屬性后繼續查詢。但是一旦我們調用了Query的end()方法之后我們將不能夠再更改Query的一些屬性。這包括調用include來定義返回結果中需要包含的信息、指定排序的屬性、指定分組的屬性、添加Criteria限制條件和調用maxResults()方法指定最大返回記錄數。
3.8 對BeanShell的支持
BeanShell是用Java寫的,能夠對Java字符串表達式進行解釋執行的一個工具。如果在實際應用中我們需要讓用戶來自定義查詢的腳本時,我們就可以使用BeanShell來對查詢腳本進行解釋執行了。使用BeanShell前我們需加入BeanShell的jar包到類路徑,筆者下面的示例中使用的是BeanShell2.0的第4個測試版本。
- @Test
- public void beanShell() throws EvalError {
- CacheManager cacheManager = CacheManager.create();
- Cache userCache = cacheManager.getCache("userCache");
- User user;
- for (int i=0; i<10; i++) {
- user = new User(i, "name"+(i%2), 25+i);
- userCache.put(new Element(user.getId(), user));
- }
- //BeanShell解釋器,需引入BeanShell相關jar包
- Interpreter interpreter = new Interpreter();
- Query query = userCache.createQuery().includeValues();
- //Interpreter進行計算的字符串中出現的變量都需要放入Interpreter的環境中
- interpreter.set("query", query);//把query放入Interpreter環境中
- //把age放入Interpreter環境中
- interpreter.set("age", userCache.getSearchAttribute("age"));
- String queryStr = "query.addCriteria(age.lt(30)).execute();";
- //BeanShell執行字符串表達式對userCache進行查詢,並返回Results
- Results results = (Results)interpreter.eval(queryStr);
- for (Result result : results.all()) {
- System.out.println(result);
- }
- results.discard();
- }
關於BeanShell的更多了解請訪問BeanShell的官方網站www.beanshell.org。
3.9 小結
縱觀整個Ehcahce中對於Cache的查詢Query,我們可以發現其基本的邏輯和規則與SQL查詢是一樣的。可以進行篩選、選擇要查詢的結果、統計、排序和分組。Ehcache中的查詢也是先通過Criteria進行篩選,再進行分組和排序。
(注:本文是基於ehcache2.8.1所寫)