(05)Ehcache缓存的查询

x33g5p2x  于2021-12-25 转载在 其他  
字(21.6k)|赞(0)|评价(0)|浏览(400)

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的方法有哪些。

| <br>Attribute****方法<br> | <br>对应Criteria实现类<br> | <br>描述<br> |
| <br>between<br> | <br>Between<br> | <br>属性值在给定的范围之间<br> |
| <br>in<br> | <br>InCollection<br> | <br>在给定的集合之中<br> |
| <br>ne<br> | <br>NotEqualTo<br> | <br>不等于给定的值<br> |
| <br>eq<br> | <br>EqualTo<br> | <br>等于给定的值<br> |
| <br>lt<br> | <br>LessThan<br> | <br>小于给定的值<br> |
| <br>le<br> | <br>LessThanOrEqual<br> | <br>小于或等于给定的值<br> |
| <br>gt<br> | <br>GreaterThan<br> | <br>大于给定的值<br> |
| <br>ge<br> | <br>GreaterThanOrEqual<br> | <br>大于或等于给定的值<br> |
| <br>ilike<br> | <br>ILike<br> | <br>匹配给定的表达式,表达式中可以使用“*”来代表任意多个字符,使用“?”来代表任意一个字符<br> |
| <br>notIlike<br> | <br>NotILike<br> | <br>不匹配给定的表达式<br> |
| <br>isNull<br> | <br>IsNull<br> | <br>等于null<br> |
| <br>notNull<br> | <br>NotNull<br> | <br>不等于null<br> |

那当我们要实现与或非的逻辑时怎么办呢?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");
query.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的查询结果中会包含对应的内容。下面用一个表格来做个展示。

| <br>Query****方法<br> | <br>描述<br> |
| <br>includeKeys()<br> | <br>查询结果中包含所存元素的key<br> |
| <br>includeValues()<br> | <br>查询结果中包含所存元素的value<br> |
| <br>includeAttribute(Attribute<?>... attributes)<br> | <br>查询结果中要包含的可查询属性<br> |
| <br>includeAggregator(Aggregator... aggregators)<br> | <br>查询结果中所要包含的统计信息,关于Aggregator将在后文介绍统计的时候进行讲解<br> |

如下的代码表示我们的查询结果中会包含元素的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所写)

相关文章