一个大集合里面过滤获取其中的部分数据是一个很常见的需求,比如在财务系统中,用户会维护汇率这样的数据:
public class ExchangeRate {
private String fromCurrency;
private String toCurrency;
private Date fromDate;
private Date thruDate;
private double rate;
}
在系统计算的时候,会用到类似这样的方法,来获取某个时间点2个货币单位之间的汇率:
public double getRate(String fromCurrency, String toCurrency, Date when)
最常见的实现方法就是用查询语句(SQL/HSQL/EJBQL)来获取,考虑到这种类型数据的2个特性:
1. 数据量比较少
2. 经常被查询到
我们往往会给它们加上Cache来提高性能,在Hibernate里面我们可以配置查询缓存来实现,考虑到Hibernate查询缓存是用查询参数做为Cache Key的特性,为了节省缓存空间,更常见的做法是缓存所有ExchangeRate,然后从这个集合里面过滤,比如说用apache的Collection Utils来实现:
return CollectionUtils.find(getAllExchangeRates(), new Predicate() {
public boolean evaluate(Object o) {
ExchangeRate r = (ExchangeRate) o;
return r.getFromCurrency().equals(fromCurrency) && r.getToCurrency().equals(toCurrency) && ...
}
}).getRate();
我们需要一个Java匿名内部类来做,这样的代码看起来不是很简洁,而且和大家熟悉的SQL是不同的风格,如果可以用一种和它们类似的方式来实现,会好很多,JoSQL ( http://josql.sourceforge.net/ ) 就是这样的工具:
JoSQL (SQL for Java Objects) provides the ability for a developer to apply a SQL statement to a collection of Java Objects. JoSQL provides the ability to search, order and group ANY Java objects and should be applied when you want to perform SQL-like queries on a collection of Java Objects.
用JoSQL来实现上面的例子:
Query q = new Query();
q.parse("select * from ExchangeRate where fromCurrency = :fromCurrency and toCurrency = :toCurrency and fromDate <= :when and thruDate >= :when");
q.setVariable("fromCurrency", fromCurrency);
q.setVariable("toCurrency", toCurrency);
q.setVariable("when", date);
return q.execute(getAllExchangeRates()).getResults().get(0).getRate();
我们可以看到和SQL的查询方式非常类似,这里只是一个JoSQl最简单的功能,在它的文档里面:http://josql.sourceforge.net/manual/examples.html还可以看到Order, Grouping, Execute On等特性,对于实现常见的用户需求来说是非常有用的。
回到上面的例子,默认的JoSQL代码对于实现这种fiter性质的功能来说还是稍显麻烦,我们可以包装一下:
JoSQLUtil.find(list, "fromCurrency = ? and toCurrency = ? and fromDate <= ? and thruDate >= ?", new Object[]{fromCurrency, toCurrency, date, date}).get(0).getRate();
JoSQLUtil里面可以处理掉一些默认的拼凑SQL,设置参数等代码,这样会变得更加简洁了。