在大多数情况下通过put和get可以满足我们的需求,但是有时候我们并不知道key,在这种情况下,我们需要通过属性的条件查询来获取相应的entry.
coherence允许我们通过QueryMap定义的接口执行指定过滤器的集合操作.
上面三个方法都接受一个简单的Filter接口参数,filter接口定义了一个基本的evaluate方法,这个方法接受一个对象,如果这个对象满足指定过滤器的条件,则返回true,反之返回false.
这种机制非常灵活,它允许你过滤出你想要的缓存对象。例如,它可以很简单实现一个在在某些时候你想要用于检索所有的账户交易的过滤器.
而在coherence中,我们可以通过其在com.tangosol.util.filter包中内置的大量过滤器来达到相应的效果.你可以通过它们来进行自定义的查询.
如上面所示,几乎所有的Java标准逻辑运算符和SqL谓词都有被覆盖。这将使我们能够构建复杂的查询表达式.我们可以定义在Java代码或SQL where子句。可惜的是,这将使得查询没有连贯性,不像查询语言一样为查询指定为一个字符串。相反,您需要编程方式创建的表达式树 进行查询,它会使事情变得有点乏味。
正如你从前面的例子所看到的,一个对象属性的查询通常作为表达的条件 ,比如accountId或时间,而定义evaluate的方法获取一个属于某个对象的属性,而过滤器接口接受这个extractors,例如 交易实例.
这意味着,我们需要一个通用的方法来提取从一个对象的属性值,换句话说,就是没有办法定义可重用的过滤器.我们将被迫为每个我们需要执行查询实现一个自定义过滤器.
- Reflection extractor
在大多数情况下,你可能会想获取某个对象的某个属性值,在这种情况下,你可以使用反射提取器ReflectionExtractor,该类接受一个执行方法名称,以及期望的值作为构造参数.它会通过放射机制调用目标对象的方法比较是否与期望值相同然后返回结果.
通常情况下,我们在创建filter的时候只传入方法名称字符串,filter默认会为我们创建ReflectionExtractor去提取相应的属性值.比如:
Filter filter = new BetweenFilter("getTime", from, to);
实际上是:
Filter filter = new BetweenFilter(
new ReflectionExtractor( "getTime"),
from, to);
我不否认ReflectionExtractor的功能,不过我不是太喜欢使用这种方式,最主要的原因是它迫使你拼出一个完整的方法名称,他要求你当你的java bean必须符合java规范而且可读性高.这种方式在.net中就变得不太适用.
不过我们可以解决这个问题,通过实现一个类似的提取器,属性提取器:
public class PropertyExtractor implements ValueExtractor, Serializable {
private final String m_propertyName ;
private transient volatile Method m_readMethod ;
public PropertyExtractor(String propertyName) {
m_propertyName = propertyName;
}
public Object extract(Object o) {
if (o == null) {
return null ;
}
Class targetClass = o.getClass();
try {
if ( readMethod == null
|| readMethod.getDeclaringClass() != targetClass) {
PropertyDescriptor pd = new PropertyDescriptor(propertyName,
o.getClass());
readMethod = pd.getReadMethod();
}
return readMethod.invoke(o);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
现在我们可以通过
PropertyExtractor来取代ReflectExtractor.
Filter filter = new BetweenFilter(new PropertyExtractor( "time"),from, to);
在本例的区别并不重要,甚至可以认为PropertyExtractor使代码难以阅读,因为我们必须明确的指定它,而不是使用以字符串作为参数的构造函数取代ReflectionExtractor来创建过滤器。然而,我们在下一节中将实现一个助手类,使过滤器创建更简单,和 PropertyExtractor允许我们会让事情尽可能简单。
note:
如果你熟悉一些流行的表达式语言,比如:MVEL,OGNL,获取SpEl,你会疑惑为什么我不用其中的一种去实现上面的值提取器?后面我不仅会使用表达式来执行属性提取操作,我还会使用更加复杂的表达式来作为提取操作.
Other built-in value extractors
而ReflectionExtractor绝对是最常使用的一个,不过也有一些其他的提取器:
- IdentityExtractor
IdentityExtractor是最简单的萃取器,它并没有从目标对象真正进行提取,而是返回目标对象本身。当你真正想要的过滤器操作缓存值本身而不是它的一个属性这个提取器可以派上用场,这是通常是在只有在值是简单的类型情况下,如一个内在的数值或字符串类型
2.
ChainedExtractor and MultiExtractor
还有两个复合值提取器,ChainedExtractor和MultiExtractor。他们两个接受数组的值提取器作为构造函数参数,但他们的使用方式不同。
ChainedExtractor提取器先从前一个filter获取到值,然后再吧他作为目标对象在进行获取值,如果我们先要获取交易对象中获取账户id:
ValueExtractor ex =
new ChainedExtractor( new ValueExtractor[] {
new ReflectionExtractor( "getId"),
new ReflectionExtractor( "getAccountId") });
这是因为交易类不提供accountid,我们需要先获取account实例在获取accountid值.
你也可以简单的对其创建:
ValueExtractor ex =
new ChainedExtractor( "getId.getAccountId");
另一方面,MultiExtractor将执行所有萃取器相同,目标对象并返回的列表中提取值。当你将很少使用这个器查询缓存时,它可以很方便当你想 只提取对象的属性的一个子集,在聚合(我们将 讨论不久),为了减少需要的数据量的传输。
3.
PofExtractor
Coherence 3.5中引入的一个特性是PofExtractor可以用来提取值POF-serialized二进制文件反序列化。这可以提供一个巨大的性能提升和减少内存足迹查询,否则必须在缓存中以evlovate过滤器为每一个对象进行反序列化
但是,这些好处只会在处理包含大对象的缓存。对于小型对象,使用初始化结构的开销来跟踪在一个二进制序列化的属性将POF的值的位置可能(从内存和性能角度)要高于反序列化的对象。
实现一个自定义的提取器
内置的值提取器基本满足大多数使用场景,但是可能会有一些情况下,需要实现一个自定义的提取器。之前我们已经实现了一个自定义值提取器,PropertyExtractor,为了提高内置ReflectionExtractor和让自己指定JavaBean属性名称,而不是完整的方法名,也有其他场景时也可能会需要。
一个原因比如你可能想实现自定义值提取器对缓存类型进行转换。例如,大多数应用程序可以使用UI控件,比如下拉框或列表框把可能的选择列表呈现给用户。假设我们需要在新客户注册界面中的一个下拉列表内显示国家信息.
我们已经有一个缓存包含所有国家,所以我们可以很容易地获得所有的值并将它们发送到客户端,使用它们来填充下拉列表。然而,在第二章中定义的类我们有很多我们不需要为了填充下拉列表的属性,如资本、货币符号,和货币,而我们只需要国家代码和名称,所以发送的任何其他信息到客户我们只会浪费网络带宽。
事实上,对于大多数,如果不是全部,菜单和列表框在一个应用程序里面,我们将只需要一个标识符,将其作为选择返回值,用于描述应该用于显示目的。这意味着我们可以定义一个类只包含两个属性:
public class LookupValue implements Serializable {
private Object m_id ;
private String m_description ;
public LookupValue(Object id, String description) {
m_id = id;
m_description = description;
}
public Object getId() {
return m_id ;
}
public String getDescription() {
return m_description ;
}
}
现在我们有一个holder类,可以用来表示任何需要查找值, 剩下的问题是如何将国家类的实例转换为LookupValue类的实例。其实很简单,我们可以编写一个定制的值提取器,将为我们做这件事
public class LookupValueExtractor extends AbstractExtractor implements
PortableObject, Serializable {
private ValueExtractor m_idExtractor;
private ValueExtractor m_descriptionExtractor;
public LookupValueExtractor(ValueExtractor idExtractor,
ValueExtractor descriptionExtractor) {
m_idExtractor = idExtractor;
m_descriptionExtractor = descriptionExtractor;
}
public Object extractFromEntry(Map.Entry entry) {
Object id = InvocableMapHelper.extractFromEntry(m_idExtractor, entry);
String description = (String) InvocableMapHelper.extractFromEntry(
m_descriptionExtractor, entry);
return new LookupValue(id, description);
}
// equals and hashCode omitted for brevity
}
实现实际上是非常简单的:我们允许用户指定两个值idExtractor和descriptionExtractor提取器,我们使用提取的值用于创建一个LookupValue实例,不过有一件事需要说明,我不是简单地实现ValueExtractor接口,我们正在扩大AbstractExtractor类和实现extractFromEntry方法。这样做的原因是,我们希望能够提取不仅id和描述输入的值,而是来自输入键
为了达到这个目的,我们依靠InvocableMapHelper类,它提供了一个实用的方法,可用于从任何实现了Map.Entry接口的对象中提取一个值.
当然,LookupValueExtractor只是其中的一部分,我们还需要一种方法对所有的对象执行这个提取器和获得的国家提取查找缓存值集合.我们将看到什么最好的方法,现在让我们回到coherence过滤器,看看我们能做写什么让复杂的查询更容易创建。
简化coherence查询
现在,你可能已经意识到的coherence的查询随着创建属性查询中使用数量的增加变得相当繁琐, 特别是如果需要使用非默认值提取器。可能最好的方法是实现一个真正的查询语言。我们可以定义一个coherence语法的过滤器将用于解析的查询表示类似sql的查询字符串解析树.
我们要做的是实现一个FilterBuilder类,它将允许我们用一种更简单的方法定义查询过滤器。虽然这种方法不会允许我们表达所有可能的查询,但是它会覆盖大量的最常见的用例.
Filter builder
FilterBuilder实现背后的想法是,许多查询使用and,or或者less等逻辑连接进行基于简单的属性比较,多属性的比较.如果你回顾一下内置的filter列表,你会发现coherence已经提供了许多的核心基础设施,我们需要做的是通过一种更为简单的方式将他们组织起来.
我们的目标是能够创建一个过滤器使用代码类似于:
Filter filter = new FilterBuilder().equals("id.accountId", 123).between("time", from, to).build();
这将使我们能够定义复杂的查询在更短和显著更可读的方式。为了支持前面的语法,我们可以实现FilterBuilder类如下:
public class FilterBuilder {
private Class defaultExtractorType;
private List<Filter> filters = new ArrayList <Filter>();
// constructors
public FilterBuilder() {
this(PropertyExtractor. class);
}
public FilterBuilder(Class defaultExtractorType) {
this.defaultExtractorType = defaultExtractorType;
}
// public members
public FilterBuilder equals(String propertyName, Object value) {
return equals(createExtractor(propertyName), value);
}
public FilterBuilder equals(ValueExtractor extractor, Object value) {
filters.add(new EqualsFilter(extractor, value));
return this ;
}
public FilterBuilder notEquals(String propertyName, Object value) {
return notEquals(createExtractor(propertyName), value);
}
public FilterBuilder notEquals(ValueExtractor extractor, Object value) {
filters.add(new NotEqualsFilter(extractor, value));
return this ;
}
public FilterBuilder greater(String propertyName, Comparable value) {
return greater(createExtractor(propertyName), value);
}
public FilterBuilder greater(ValueExtractor extractor, Comparable value) {
filters.add(new GreaterFilter(extractor, value));
return this ;
}
// and so on...
}
基本上,我们为每个内置过滤器:实现两个重载方法 第一个是用提取器作为第一个参数,另外一个接受一个字符串来创建一个值提取器。
不过,不想内置的过滤器,我们不能自动的创建一个reflectExtractor,所以我们需要做一些额外的事,我们需要委托给一个createExtractor的工厂方法的提取器来创建:
protected ValueExtractor createExtractor(String propertyName) {
if (propertyName.indexOf( '.') >= 0) {
return new ChainedExtractor(
createExtractorArray(propertyName.split(".")));
}
if (propertyName.indexOf( ',') >= 0) {
return new MultiExtractor(
createExtractorArray(propertyName.split(",")));
}
return createDefaultExtractor(propertyName);
}
正如您可以看到的,如果一个以圆点分隔字符串指定的属性名,我们会用ChainedExtractor创建一个实例。同样,一个以逗号分隔的属性名我们将创建用MultiExtractor创建一个实例。
对于所有其他属性,我们将委托提取器创建createDefaultExtractor
protected ValueExtractor createDefaultExtractor(String propertyName) {
Constructor ctor = getConstructor(defaultExtractorType);
return (ValueExtractor ) ctor.newInstance(propertyName);
}
这使我们在使用过滤器的时候能够根据具体问题具体分析控制一个值提取器的使用。在大多数情况下,默认PropertyExtractor应该满足需求,但你可以通过指定一个不同的萃取器很容易地改变行为类构造函数参数:
Filter filter = new FilterBuilder(ReflectionExtractor.class).equals("getCustomerId", 123).greater("getTotal", 1000.0)
.build();
Obtaining query results
最简单获取结果的查询方法是调用QueryMap.entrySet方法:
Filter filter = ...;
Set<Map.Entry> results = cache.entrySet(filter);
他会返回该缓存所有的key和value集合,其中可能有些值并不是你所需要的,大多情况下你只需要其中的部分值,因此你需要迭代它去提取出你所需要的值:
List values = new ArrayList(results.size());
for (Map.Entry entry : entries) {
values.add(entry.getValue());
}
这样做几次之后,对于这个任务你可能想要创建一个实用程序方法。因为所有的查询应封装在不同存储库实现,我们可以简单地添加下面的实用方法在我们的AbstractCoherenceRepository类中:
public abstract class AbstractCoherenceRepository <K, V extends
Entity<K>> {
...
protected Collection<V> queryForValues(Filter filter) {
Set<Map.Entry<K, V>> entries = getCache().entrySet(filter);
return extractValues(entries);
}
protected Collection<V> queryForValues(Filter filter,
Comparator comparator) {
Set<Map.Entry<K, V>> entries =
getCache().entrySet(filter, comparator);
return extractValues(entries);
}
private Collection<V> extractValues(Set<Map.Entry<K, V>> entries) {
List<V> values = new ArrayList<V>(entries.size());
for (Map.Entry<K, V> entry : entries) {
values.add(entry.getValue());
}
return values;
}
使用数据关联控制查询范围
正如前面我们提到的,关联性可以提高coherence的性能,因为他将相关的数据聚集在一起.优化了查询.而不是在所有节点上并行执行查询聚合结果,coherence可以在单个节点上执行它,因为数据关联进行了聚集,所有的结果将在特定的查询节点。这有效地减少了搜索的对象数量大约C / N,C 是缓存中对象的总数是执行查询,和N是集群分区的数量。
不过,这个优化不会自动的,你必须是用KeyAssociatedFilter对目标分区进行显示搜查.
Filter query = ...;
Filter filter = new KeyAssociatedFilter (query , key);
在前面的示例中,我们创建一个KeyAssociatedFilter包装了我们想要执行的查询。它的构造函数的第二个参数是缓存键决定了那个分区执行搜索。
让这一切更具体,让我们来看看最后的实现代码 为示例应用程序返回账户交易为一个特定的时期。 首先,我们需要添加getTransactions方法Account类:
public Collection<Transaction> getTransactions(Date from, Date to) {
return getTransactionRepository().findTransactions( m_id, from, to);
}
CoherenceTransactionRepository:
public Collection<Transaction> findTransactions(Long accountId, Date from,
Date to) {
Filter filter = new FilterBuilder().equals("id.accountId" , accountId)
.between( "time", from, to).build();
return queryForValues(new KeyAssociatedFilter (filter, accountId),
new DefaultTransactionComparator());
}
正如上面所示,我们通过account的标示进行目标查询,确保结果是按交易排序后,将其通过DefaultTransactionComparator传递给queryForValues helper方法.这可以确保查询交易只在指定accountid所在的分区进行执行.
Querying near cache
当你通过entrySet去查询一个near cache 的结果集的时候,这种方式是十分低效的.