Solrj已经是很强大的solr客户端了。它本身就包装了httpCliet,以完全对象的方式对solr进行交互。很小很好很强大。
不过在实际使用中,设置SolrQuery 的过程中,为了设置多个搜索条件和排序规则等等参数,我们往往会陷入并接字符串的地步,实在是很丑陋,不符合面向对象的思想。扩展性几乎为0,。基于这点,开发了一个小东西,我们只需要设置搜索对象,将对象扔给后台就可以了。
比如,我们搭建的solr服务支持某10个字段的搜索,我们要搜索其中的一些,那么我们只需要传入要搜索的对象POJO,将要搜索的字段内容,set到POJO对象对应额字段即可。
比如如下一个类:
- package org.uppower.tnt.biz.core.manager.blog.dataobject;
- /**
- * @author yingmu
- * @version 2010-7-20 下午01:00:55
- */
- public class SolrPropertyDO {
- private String auction_id;
- private String opt_tag;
- private String exp_tag;
- private String title;
- private String desc;
- private String brand;
- private String category;
- private String price;
- private String add_prov;
- private String add_city;
- private String quality;
- private String flag;
- private String sales;
- private String sellerrate;
- private String selleruid;
- private String ipv15;
- public String getAuction_id() {
- return auction_id;
- }
- public void setAuction_id(String auctionId) {
- auction_id = auctionId;
- }
- ……
- public String getExp_tag() {
- return exp_tag;
- }
- public void setExp_tag(String expTag) {
- exp_tag = expTag;
- }
- }
那么我们在定义搜索对象时候,就按照如下设置:
- SolrPropertyDO propertyDO = new SolrPropertyDO();
- propertyDO.setAdd_city("(杭州AND成都)OR北京");
- propertyDO.setTitle("丝绸OR剪刀");
- ……
设置排序条件,也是类似的做法:
- SolrPropertyDO compositorDO = new SolrPropertyDO();
- compositorDO.setPrice ("desc");
- compositorDO.setQuality ("asc");
- ……
将定义好的两个对象扔给后面的接口就可以了。
接口函数querySolrResult传入四个参数,其中包含搜索字段对象,排序条件对象。为了提供类似limit的操作,用于分页查询,提供了startIndex和pageSize。
函数querySolrResultCount是单纯为了获得搜索条数,配合分页使用。
以下是定义的接口:
- package org.uppower.tnt.biz.core.manager.blog;
- import java.util.List;
- import org.uppower.tnt.biz.core.manager.isearch.dataobject.SolrPropertyDO;
- /**
- * @author yingmu
- * @version 2010-7-20 下午03:51:15
- */
- public interface SolrjOperator {
- /**
- * 获得搜索结果
- *
- * @param propertyDO
- * @param compositorDO
- * @param startIndex
- * @param pageSize
- * @return
- * @throws Exception
- */
- public List<Object> querySolrResult(Object propertyDO,
- Object compositorDO, Long startIndex, Long pageSize)
- throws Exception;
- /**
- * 获得搜索结果条数
- *
- * @param propertyDO
- * @param compositorDO
- * @return
- * @throws Exception
- */
- public Long querySolrResultCount(SolrPropertyDO propertyDO,
- Object compositorDO) throws Exception;
- }
实现逻辑为,首先将传入的两个实体对象,解析为<K,V>结构的Map当中,将解析完成的Map放入solrj实际的搜索对象当中。返回的对象为solrj的API提供的SolrDocument,其中结果数量为直接返回SolrDocumentList对象的getNumFound()
具体实现类:
- package org.uppower.tnt.biz.core.manager.blog;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.TreeMap;
- import org.apache.solr.common.SolrDocumentList;
- import org.uppower.tnt.biz.core.manager.isearch.common.SolrjCommonUtil;
- import org.uppower.tnt.biz.core.manager.isearch.dataobject.SolrPropertyDO;
- import org.uppower.tnt.biz.core.manager.isearch.solrj.SolrjQuery;
- /**
- * @author yingmu
- * @version 2010-7-20 下午03:51:15
- */
- public class DefaultSolrOperator implements SolrjOperator {
- private Logger logger = LoggerFactory.getLogger(this.getClass());
- private SolrjQuery solrjQuery;
- public void setSolrjQuery(SolrjQuery solrjQuery) {
- this.solrjQuery = solrjQuery;
- }
- @Override
- public List<Object> querySolrResult(Object propertyDO,
- Object compositorDO, Long startIndex, Long pageSize)
- throws Exception {
- Map<String, String> propertyMap = new TreeMap<String, String>();
- //排序有顺序,使用TreeMap
- Map<String, String> compositorMap = new TreeMap<String, String>();
- try {
- propertyMap = SolrjCommonUtil.getSearchProperty(propertyDO);
- compositorMap = SolrjCommonUtil.getSearchProperty(compositorDO);
- } catch (Exception e) {
- logger.error("SolrjCommonUtil.getSearchProperty() is error !"+ e);
- }
- SolrDocumentList solrDocumentList = solrjQuery.query(propertyMap, compositorMap,
- startIndex, pageSize);
- List<Object> resultList = new ArrayList<Object>();
- for (int i = 0; i < solrDocumentList.size(); i++) {
- resultList.add(solrDocumentList.get(i));
- }
- return resultList;
- }
- @Override
- public Long querySolrResultCount(SolrPropertyDO propertyDO,
- Object compositorDO) throws Exception {
- Map<String, String> propertyMap = new TreeMap<String, String>();
- Map<String, String> compositorMap = new TreeMap<String, String>();
- try {
- propertyMap = SolrjCommonUtil.getSearchProperty(propertyDO);
- compositorMap = SolrjCommonUtil.getSearchProperty(compositorDO);
- } catch (Exception e) {
- logger.error("SolrjCommonUtil.getSearchProperty() is error !" + e);
- }
- SolrDocumentList solrDocument = solrjQuery.query(propertyMap, compositorMap,
- null, null);
- return solrDocument.getNumFound();
- }
- }
其中,对象的解析式利用反射原理,将实体对象中不为空的值,以映射的方式,转化为一个Map,其中排序对象在转化的过程中,使用TreeMap,保证其顺序性。
解析公共类实现如下:
- package org.uppower.tnt.biz.core.manager.blog.common;
- import java.lang.reflect.Field;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * @author yingmu
- * @version 2010-7-20 下午01:07:15
- */
- public class SolrjCommonUtil {
- public static Map<String, String> getSearchProperty(Object model)
- throws NoSuchMethodException, IllegalAccessException,
- IllegalArgumentException, InvocationTargetException {
- Map<String, String> resultMap = new TreeMap<String, String>();
- // 获取实体类的所有属性,返回Field数组
- Field[] field = model.getClass().getDeclaredFields();
- for (int i = 0; i < field.length; i++) { // 遍历所有属性
- String name = field[i].getName(); // 获取属性的名字
- // 获取属性的类型
- String type = field[i].getGenericType().toString();
- if (type.equals("class java.lang.String")) { // 如果type是类类型,则前面包含"class ",后面跟类名
- Method m = model.getClass().getMethod(
- "get" + UpperCaseField(name));
- String value = (String) m.invoke(model); // 调用getter方法获取属性值
- if (value != null) {
- resultMap.put(name, value);
- }
- }
- }
- return resultMap;
- }
- // 转化字段首字母为大写
- private static String UpperCaseField(String fieldName) {
- fieldName = fieldName.replaceFirst(fieldName.substring(0, 1), fieldName
- .substring(0, 1).toUpperCase());
- return fieldName;
- }
- }
搜索直接调用solr客户端solrj,基本逻辑为循环两个解析之后的TreeMap,设置到SolrQuery当中,最后直接调用solrj的API,获得搜索结果。最终将搜索结构以List<Object>的形式返回。
具体实现:
- package org.uppower.tnt.biz.core.manager.blog.solrj;
- import java.net.MalformedURLException;
- import java.util.Map;
- import org.apache.solr.client.solrj.SolrQuery;
- import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
- import org.apache.solr.client.solrj.response.QueryResponse;
- import org.apache.solr.common.SolrDocumentList;
- /**
- * @author yingmu
- * @version 2010-7-20 下午02:57:04
- */
- public class SolrjQuery {
- private String url;
- private Integer soTimeOut;
- private Integer connectionTimeOut;
- private Integer maxConnectionsPerHost;
- private Integer maxTotalConnections;
- private Integer maxRetries;
- private CommonsHttpSolrServer solrServer = null;
- private final static String ASC = "asc";
- public void init() throws MalformedURLException {
- solrServer = new CommonsHttpSolrServer(url);
- solrServer.setSoTimeout(soTimeOut);
- solrServer.setConnectionTimeout(connectionTimeOut);
- solrServer.setDefaultMaxConnectionsPerHost(maxConnectionsPerHost);
- solrServer.setMaxTotalConnections(maxTotalConnections);
- solrServer.setFollowRedirects(false);
- solrServer.setAllowCompression(true);
- solrServer.setMaxRetries(maxRetries);
- }
- public SolrDocumentList query(Map<String, String> propertyMap,
- Map<String, String> compositorMap, Long startIndex, Long pageSize)
- throws Exception {
- SolrQuery query = new SolrQuery();
- // 设置搜索字段
- if (null == propertyMap) {
- throw new Exception("搜索字段不可为空!");
- } else {
- for (Object o : propertyMap.keySet()) {
- StringBuffer sb = new StringBuffer();
- sb.append(o.toString()).append(":");
- sb.append(propertyMap.get(o));
- String queryString = addBlank2Expression(sb.toString());
- query.setQuery(queryString);
- }
- }
- // 设置排序条件
- if (null != compositorMap) {
- for (Object co : compositorMap.keySet()) {
- if (ASC == compositorMap.get(co)
- || ASC.equals(compositorMap.get(co))) {
- query.addSortField(co.toString(), SolrQuery.ORDER.asc);
- } else {
- query.addSortField(co.toString(), SolrQuery.ORDER.desc);
- }
- }
- }
- if (null != startIndex) {
- query.setStart(Integer.parseInt(String.valueOf(startIndex)));
- }
- if (null != pageSize && 0L != pageSize.longValue()) {
- query.setRows(Integer.parseInt(String.valueOf(pageSize)));
- }
- try {
- QueryResponse qrsp = solrServer.query(query);
- SolrDocumentList docs = qrsp.getResults();
- return docs;
- } catch (Exception e) {
- throw new Exception(e);
- }
- }
- private String addBlank2Expression(String oldExpression) {
- String lastExpression;
- lastExpression = oldExpression.replace("AND", " AND ").replace("NOT",
- " NOT ").replace("OR", " OR ");
- return lastExpression;
- }
- public Integer getMaxRetries() {
- return maxRetries;
- }
- ……
- public void setMaxTotalConnections(Integer maxTotalConnections) {
- this.maxTotalConnections = maxTotalConnections;
- }
- }
整个实现是在Spring的基础上完成的,其中SolrjQuery的init()方法在Spring容器启动是初始化。Init()方法内的属性,也是直接注入的。上层与下层之间也完全用注入的方式解决。具体配置就不贴不出来了,大家都会。
整个代码很简陋,但是几乎支持了你想要搜索的条件设置,而且不会暴露任何与solr相关的内容给上层调用,使整个搜索几乎以sql语言的思想在设置条件。